diff --git a/WebContent/WEB-INF/springapp-servlet.xml b/WebContent/WEB-INF/springapp-servlet.xml index 067b265..bdcc5db 100644 --- a/WebContent/WEB-INF/springapp-servlet.xml +++ b/WebContent/WEB-INF/springapp-servlet.xml @@ -102,6 +102,7 @@ + diff --git a/http/http-client.env.json b/http/http-client.env.json new file mode 100644 index 0000000..0052354 --- /dev/null +++ b/http/http-client.env.json @@ -0,0 +1,8 @@ +{ + "dev": { + "emsUrl": "http://192.168.240.176:30100/monitoring" + }, + "local": { + "emsUrl": "http://127.0.0.1:30100/monitoring" + } +} \ No newline at end of file diff --git a/http/obp-GwMetric.http b/http/obp-GwMetric.http new file mode 100644 index 0000000..2cc592d --- /dev/null +++ b/http/obp-GwMetric.http @@ -0,0 +1,6 @@ +### Retrieve Gateway Metrics After a Specific Time Slice +GET {{emsUrl}}/kjb/gw-metrics/after-timeslice/2025-12-02_01:00:00.json + + +### Pretty Print +GET {{emsUrl}}/kjb/gw-metrics/after-timeslice/2025-12-02_01:00:00.json?pretty=true \ No newline at end of file diff --git a/kjb-docs/jbr.md b/kjb-docs/jbr.md new file mode 100644 index 0000000..dbc5d35 --- /dev/null +++ b/kjb-docs/jbr.md @@ -0,0 +1,75 @@ +# πŸš€ Spring Legacy (Non-Boot) ν•«μŠ€μ™‘ ν™˜κ²½ ꡬ좕 κ°€μ΄λ“œ + +λ³Έ κ°€μ΄λ“œλŠ” 폐쇄망(Air-gapped) ν™˜κ²½μ—μ„œ **JRebel** λŒ€μ•ˆμœΌλ‘œ \*\*JetBrains Runtime(JBR)\*\*κ³Ό **HotswapAgent**λ₯Ό μ‚¬μš©ν•˜μ—¬, μ„œλ²„ μž¬μ‹œμž‘ 없이 Java μ½”λ“œλ₯Ό μ¦‰μ‹œ λ°˜μ˜ν•˜λŠ” 방법을 μ„€λͺ…ν•©λ‹ˆλ‹€. + +## 1\. 외뢀망 μ€€λΉ„λ¬Ό (λ°˜μž… 리슀트) + +νμ‡„λ§μœΌλ‘œ λ°˜μž…ν•΄μ•Ό ν•  ν•„μˆ˜ νŒŒμΌμž…λ‹ˆλ‹€. + +| ν•­λͺ© | μš©λ„ | λ‹€μš΄λ‘œλ“œ 링크 (μ˜ˆμ‹œ) | +| :--- | :--- | :--- | +| **JBR (JetBrains Runtime)** | DCEVM이 λ‚΄μž₯된 JDK | [JBR GitHub Releases](https://github.com/JetBrains/JetBrainsRuntime/releases) | +| **HotswapAgent.jar** | Spring Bean μ„€μ • κ°±μ‹ μš© | [HotswapAgent Releases](https://github.com/HotswapProjects/HotswapAgent/releases) | + +> **주의**: JBR 버전은 ν”„λ‘œμ νŠΈμ˜ Java 버전(8, 11, 17 λ“±)κ³Ό μΌμΉ˜μ‹œμΌœ λ‹€μš΄λ‘œλ“œν•˜μ„Έμš”. + +----- + +## 2\. IDE(IntelliJ / Eclipse) ν™˜κ²½ μ„€μ • + +### 2.1 JDK(JBR) 등둝 + +1. λ°˜μž…ν•œ JBR 압좕을 ν’‰λ‹ˆλ‹€ (예: `C:\dev\jbr`). +2. **IntelliJ**: `Project Structure` \> `SDKs` \> `Add JDK` ν›„ ν•΄λ‹Ή 경둜 μ§€μ •. +3. **Eclipse**: `Preferences` \> `Java` \> `Installed JREs` \> `Add` ν›„ ν•΄λ‹Ή 경둜 μ§€μ •. + +### 2.2 VM μ˜΅μ…˜ μΆ”κ°€ + +μ„œλ²„(Tomcat λ“±)의 μ‹€ν–‰ ꡬ성(Run/Debug Configuration)에 μ•„λž˜μ˜ **VM Options**λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€. + +```bash +# 클래슀 ꡬ쑰 λ³€κ²½(ν•„λ“œ/λ©”μ„œλ“œ μΆ”κ°€) ν—ˆμš© +-XX:+AllowEnhancedClassRedefinition + +# Spring Bean μ„€μ • 및 λ¦¬μ†ŒμŠ€ μ‹€μ‹œκ°„ 반영 (경둜 주의) +-javaagent:C:\path\to\hotswap-agent.jar +``` + +----- + +## 3\. IDE별 μ„ΈλΆ€ μ΅œμ ν™” μ„€μ • + +### IntelliJ IDEA + +1. **컴파일러 μ„€μ •**: + - `Settings` \> `Build, Execution, Deployment` \> `Compiler` + - **Build project automatically** 체크 +2. **κ³ κΈ‰ μ„€μ •**: + - `Settings` \> `Advanced Settings` + - **Allow auto-make to start even if developed application is currently running** 체크 +3. **μ—…λ°μ΄νŠΈ μ •μ±…**: + - Tomcat μ„€μ • λ‚΄ `On 'Update' action` 및 `On frame deactivation`을 **Update classes and resources**둜 λ³€κ²½. + +### Eclipse + +1. **Auto Build**: `Project` \> `Build Automatically` 체크 확인. +2. **Debug λͺ¨λ“œ**: λ°˜λ“œμ‹œ **Debug** λͺ¨λ“œλ‘œ μ„œλ²„λ₯Ό μ‹€ν–‰ν•΄μ•Ό ν•«μŠ€μ™‘μ΄ μž‘λ™ν•©λ‹ˆλ‹€. + +----- + +## 4\. μ‹€μ œ 개발 μ›Œν¬ν”Œλ‘œμš° + +1. **μ„œλ²„ μ‹€ν–‰**: λ°˜λ“œμ‹œ **Debug λͺ¨λ“œ**둜 μ‹€ν–‰ν•©λ‹ˆλ‹€. +2. **μ½”λ“œ μˆ˜μ •**: λ©”μ„œλ“œ 둜직, ν•„λ“œ μΆ”κ°€, μƒˆλ‘œμš΄ `@Service` 생성 등을 μˆ˜ν–‰ν•©λ‹ˆλ‹€. +3. **반영 (Compile)**: + - **IntelliJ**: `Ctrl + Shift + F9` (ν˜„μž¬ 파일 재컴파일) λ˜λŠ” `Ctrl + F9` (전체 λΉŒλ“œ). + - **Eclipse**: μ €μž₯(`Ctrl + S`) μ‹œ μžλ™ 컴파일 및 반영. +4. **확인**: μ„œλ²„ μž¬μ‹œμž‘ 없이 λΈŒλΌμš°μ €μ—μ„œ μƒˆλ‘œκ³ μΉ¨ν•˜μ—¬ κ²°κ³Όλ₯Ό ν™•μΈν•©λ‹ˆλ‹€. + +----- + +## 5\. μ£Όμš” μ£Όμ˜μ‚¬ν•­ (Troubleshooting) + +* **상속 ꡬ쑰 λ³€κ²½**: 클래슀의 λΆ€λͺ¨λ₯Ό λ°”κΎΈκ±°λ‚˜ μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ³€κ²½ν•˜λŠ” μˆ˜μ€€μ˜ 큰 μˆ˜μ •μ€ μ„œλ²„ μž¬μ‹œμž‘μ΄ ν•„μš”ν•  수 μžˆμŠ΅λ‹ˆλ‹€. +* **정적 λ³€μˆ˜(Static)**: `static` ν•„λ“œμ˜ μ΄ˆκΈ°κ°’ 변경은 ν•«μŠ€μ™‘ 후에도 이전 값이 μœ μ§€λ  수 μžˆμŠ΅λ‹ˆλ‹€. +* **폐쇄망 라이브러리**: `hotswap-agent.jar` 외에 νŠΉμ • ν”„λ ˆμž„μ›Œν¬(예: MyBatis, Hibernate) μ „μš© ν”ŒλŸ¬κ·ΈμΈμ΄ ν•„μš”ν•œ 경우, HotswapAgent μ„€μ • 파일(`hotswap-agent.properties`)을 톡해 μ œμ–΄ν•  수 μžˆμŠ΅λ‹ˆλ‹€. \ No newline at end of file diff --git a/src/main/java/com/eactive/eai/rms/common/advice/EmptyJsonResponseAdviceController.java b/src/main/java/com/eactive/eai/rms/common/advice/EmptyJsonResponseAdviceController.java index 4e8ae30..525f5be 100644 --- a/src/main/java/com/eactive/eai/rms/common/advice/EmptyJsonResponseAdviceController.java +++ b/src/main/java/com/eactive/eai/rms/common/advice/EmptyJsonResponseAdviceController.java @@ -28,7 +28,7 @@ public class EmptyJsonResponseAdviceController implements ResponseBodyAdvice { + + private static final Logger log = LoggerFactory.getLogger(EndpointLogger.class); + + private boolean logged = false; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + // 쀑볡 좜λ ₯ λ°©μ§€ (parent/child context) + if (logged) { + return; + } + + ApplicationContext ctx = event.getApplicationContext(); + + try { + RequestMappingHandlerMapping mapping = ctx.getBean(RequestMappingHandlerMapping.class); + Map methods = mapping.getHandlerMethods(); + + log.debug("========== Registered HTTP Endpoints ({}) ==========", methods.size()); + + methods.entrySet().stream() + .sorted((e1, e2) -> e1.getKey().toString().compareTo(e2.getKey().toString())) + .forEach(entry -> { + RequestMappingInfo info = entry.getKey(); + HandlerMethod method = entry.getValue(); + log.debug("{} -> {}.{}", + info.getPatternsCondition(), + method.getBeanType().getSimpleName(), + method.getMethod().getName()); + }); + + log.debug("========== End of Endpoints =========="); + logged = true; + + } catch (Exception e) { + log.warn("Failed to log endpoints: {}", e.getMessage()); + } + } +} diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java index df92b9b..164bc3f 100644 --- a/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java +++ b/src/main/java/com/eactive/eai/rms/onl/apim/obp/ObpGwMetricController.java @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import java.nio.charset.StandardCharsets; @@ -27,7 +28,7 @@ public class ObpGwMetricController implements InterceptorSkipController { private static final Logger log = LoggerFactory.getLogger(ObpGwMetricController.class); private static final DateTimeFormatter PATH_FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss"); + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss"); private final ObpGwMetricManService obpGwMetricManService; @@ -39,15 +40,18 @@ public class ObpGwMetricController implements InterceptorSkipController { * νŠΉμ • μ‹œκ°„λŒ€μ˜ GwMetric 데이터 쑰회 * * @param datetime 쑰회 μ‹œκ°„λŒ€ (ν˜•μ‹: yyyy-MM-dd_HH:mm:ss) + * @param pretty trueλ©΄ JSON λ“€μ—¬μ“°κΈ° 적용 * @return JSON Array */ @RequestMapping( - value = "/kjb/gw-metrics/{datetime}", + value = "/kjb/gw-metrics/{datetime}.json", produces = "application/json;charset=utf-8" ) @ResponseBody - public String getGwMetrics(@PathVariable("datetime") String datetime) { - log.debug("GwMetric 쑰회 μš”μ²­: datetime={}", datetime); + public String getGwMetrics( + @PathVariable("datetime") String datetime, + @RequestParam(value = "pretty", required = false) Boolean pretty) { + log.debug("GwMetric 쑰회 μš”μ²­: datetime={}, pretty={}", datetime, pretty); long startTime = System.currentTimeMillis(); try { @@ -59,11 +63,7 @@ public class ObpGwMetricController implements InterceptorSkipController { List metrics = obpGwMetricManService.findByTimeslice(timeslice); // JSON λ³€ν™˜ (GSON) - Gson gson = new GsonBuilder() - .serializeNulls() - .create(); - - String json = gson.toJson(metrics); + String json = toJson(metrics, pretty); // 응닡 크기 계산 (UTF-8 κΈ°μ€€) int jsonBytes = json.getBytes(StandardCharsets.UTF_8).length; @@ -85,15 +85,18 @@ public class ObpGwMetricController implements InterceptorSkipController { * νŠΉμ • μ‹œκ°„λŒ€ μ΄ν›„μ˜ GwMetric 데이터 쑰회 * * @param datetime 쑰회 μ‹œμž‘ μ‹œκ°„λŒ€ (ν˜•μ‹: yyyy-MM-dd_HH:mm:ss) + * @param pretty trueλ©΄ JSON λ“€μ—¬μ“°κΈ° 적용 * @return JSON Array (ν•΄λ‹Ή μ‹œκ°„λŒ€ μ΄ν›„μ˜ λͺ¨λ“  μ§€ν‘œ) */ @RequestMapping( - value = "/kjb/gw-metrics/after-timeslice/{datetime}", + value = "/kjb/gw-metrics/after-timeslice/{datetime}.json", produces = "application/json;charset=utf-8" ) @ResponseBody - public String getGwMetricsAfterTimeslice(@PathVariable("datetime") String datetime) { - log.debug("GwMetric after-timeslice 쑰회 μš”μ²­: datetime={}", datetime); + public String getGwMetricsAfterTimeslice( + @PathVariable("datetime") String datetime, + @RequestParam(value = "pretty", required = false) Boolean pretty) { + log.debug("GwMetric after-timeslice 쑰회 μš”μ²­: datetime={}, pretty={}", datetime, pretty); long startTime = System.currentTimeMillis(); try { @@ -105,11 +108,7 @@ public class ObpGwMetricController implements InterceptorSkipController { List metrics = obpGwMetricManService.findByTimesliceAfter(timeslice); // JSON λ³€ν™˜ (GSON) - Gson gson = new GsonBuilder() - .serializeNulls() - .create(); - - String json = gson.toJson(metrics); + String json = toJson(metrics, pretty); // 응닡 크기 계산 (UTF-8 κΈ°μ€€) int jsonBytes = json.getBytes(StandardCharsets.UTF_8).length; @@ -127,6 +126,21 @@ public class ObpGwMetricController implements InterceptorSkipController { } } + /** + * 객체λ₯Ό JSON λ¬Έμžμ—΄λ‘œ λ³€ν™˜ + * + * @param object λ³€ν™˜ν•  객체 + * @param pretty trueλ©΄ λ“€μ—¬μ“°κΈ° 적용 + * @return JSON λ¬Έμžμ—΄ + */ + private String toJson(Object object, Boolean pretty) { + GsonBuilder builder = new GsonBuilder().serializeNulls(); + if (Boolean.TRUE.equals(pretty)) { + builder.setPrettyPrinting(); + } + return builder.create().toJson(object); + } + /** * λ°”μ΄νŠΈ 크기λ₯Ό 읽기 μ‰¬μš΄ ν˜•μ‹μœΌλ‘œ λ³€ν™˜ * 예: 1024 -> "1.0 KB", 1048576 -> "1.0 MB" diff --git a/src/main/resources/hotswap-agent.properties b/src/main/resources/hotswap-agent.properties index 3371a8b..cc2d7ce 100644 --- a/src/main/resources/hotswap-agent.properties +++ b/src/main/resources/hotswap-agent.properties @@ -1,9 +1,81 @@ -# HotSwapAgent μ„€μ • -# Spring κ΄€λ ¨ -Spring.reload=true -# Hibernate κ΄€λ ¨ -Hibernate.reload=true -# 둜그 레벨 -logLevel=INFO -# μžλ™ Hot Swap ν™œμ„±ν™” +# ============================================================ +# HotswapAgent Configuration for eapim-admin (Spring Legacy) +# ============================================================ + +# \uC790\uB3D9 \uD56B\uC2A4\uC651 \uD65C\uC131\uD654 autoHotswap=true + +# \uB85C\uAE45 \uB808\uBCA8 (TRACE, DEBUG, INFO, WARNING, ERROR) +LOGGER.level=INFO + +# ============================================================ +# \uBA85\uC2DC\uC801 \uD50C\uB7EC\uADF8\uC778 \uBE44\uD65C\uC131\uD654 (\uC27C\uD45C \uAD6C\uBD84) +# ============================================================ +disabledPlugins=org.hotswap.agent.plugin.ibatis.IBatisPlugin,org.hotswap.agent.plugin.mybatis.MyBatisPlugin + +# ============================================================ +# Core Plugin \uD65C\uC131\uD654 +# ============================================================ + +# Spring Framework (\uD544\uC218) - Bean \uC7AC\uB4F1\uB85D +spring.enabled=true +spring.basePackagePrefix=com.eactive. + +# Hibernate/JPA - \uC5D4\uD2F0\uD2F0 \uBCC0\uACBD \uAC10\uC9C0 +hibernate.enabled=true + +# Logback - \uB85C\uADF8 \uC124\uC815 \uB9AC\uB85C\uB4DC +logback.enabled=true + +# Proxy - AOP \uD504\uB85D\uC2DC \uC7AC\uC0DD\uC131 +proxy.enabled=true + +# ============================================================ +# \uBE44\uD65C\uC131\uD654 Plugin (\uBBF8\uC0AC\uC6A9 \uB610\uB294 \uCDA9\uB3CC \uBC29\uC9C0) +# ============================================================ + +# iBatis (Spring\uC758 SqlMapClientFactoryBean\uACFC \uCDA9\uB3CC) +ibatis.enabled=false +mybatis.enabled=false +jsf.enabled=false +seam.enabled=false +wicket.enabled=false +mojarra.enabled=false +omnifaces.enabled=false +el.enabled=false +weld.enabled=false +owb.enabled=false +cdi.enabled=false +resteasy.enabled=false +jersey1.enabled=false +jersey2.enabled=false + +# ============================================================ +# Watch Resources (\uB9AC\uC18C\uC2A4 \uBCC0\uACBD \uAC10\uC9C0) +# ============================================================ + +# \uD074\uB798\uC2A4\uD328\uC2A4 \uC678 \uCD94\uAC00 \uAC10\uC2DC \uACBD\uB85C (XML \uC124\uC815 \uB4F1) +watchResources=src/main/resources,WebContent/WEB-INF + +# ============================================================ +# Spring \uC804\uC6A9 \uC124\uC815 +# ============================================================ + +# \uCEF4\uD3EC\uB10C\uD2B8 \uC2A4\uCE94 \uBCA0\uC774\uC2A4 \uD328\uD0A4\uC9C0 +spring.scanBasePackage=com.eactive.eai,com.eactive.apim + +# \uD504\uB85D\uC2DC \uC7AC\uC0DD\uC131 (AOP, @Transactional \uC0AC\uC6A9 \uC2DC \uD544\uC218) +spring.proxyRegeneration=true + +# Bean \uC815\uC758 \uBCC0\uACBD \uC2DC \uB9AC\uB85C\uB4DC +spring.reloadBeanDefinition=true + +# ============================================================ +# \uB514\uBC84\uAE45 (\uBB38\uC81C \uBC1C\uC0DD \uC2DC \uD65C\uC131\uD654) +# ============================================================ + +# \uC0C1\uC138 \uB85C\uADF8 \uCD9C\uB825 (\uD544\uC694\uC2DC DEBUG\uB85C \uBCC0\uACBD) +# LOGGER.level=DEBUG + +# \uD074\uB798\uC2A4 \uBCC0\uACBD \uCD94\uC801 +# LOGGER.org.hotswap.agent.plugin.spring=DEBUG