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