From 01ca4575ba28529baaad915528f45d014d534a96 Mon Sep 17 00:00:00 2001 From: Haroon Khan Date: Mon, 31 Jan 2022 12:45:28 +0000 Subject: [PATCH] [JAVA-9604] Refactor and code clean-up for Metrics article --- .../spring-boot-actuator/README.md | 1 + .../spring-boot-actuator/pom.xml | 18 +++ .../baeldung/metrics/MetricsApplication.java | 42 ++++++ .../java/com/baeldung/metrics}/WebConfig.java | 13 +- .../metrics/controller/MetricsController.java | 41 ++++++ .../metrics/filter}/MetricFilter.java | 30 +++-- .../service}/ActuatorMetricService.java | 67 +++++----- .../service}/CustomActuatorMetricService.java | 54 ++++---- .../service/InMemoryMetricService.java | 112 ++++++++++++++++ .../metrics/service/MetricService.java | 7 + .../resources/application-metrics.properties | 9 ++ .../src/main/webapp/WEB-INF/api-servlet.xml | 0 .../webapp/WEB-INF/view/metrics}/graph.jsp | 2 +- .../webapp/WEB-INF/view/metrics}/homepage.jsp | 0 .../src/main/webapp/WEB-INF/web.xml | 4 +- .../MetricsApplicationIntegrationTest.java | 64 +++++++++ .../spring-rest-testing/README.md | 1 - .../spring-rest-testing/pom.xml | 28 ---- .../java/com/baeldung/spring/Application.java | 21 +-- .../web/controller/RootController.java | 52 -------- .../web/metric/IActuatorMetricService.java | 5 - .../metric/ICustomActuatorMetricService.java | 8 -- .../baeldung/web/metric/IMetricService.java | 14 -- .../baeldung/web/metric/MetricService.java | 122 ------------------ .../src/main/resources/application.properties | 1 - 25 files changed, 373 insertions(+), 343 deletions(-) create mode 100644 spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java rename {spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring => spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics}/WebConfig.java (81%) create mode 100644 spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/controller/MetricsController.java rename {spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric => spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter}/MetricFilter.java (64%) rename {spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric => spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service}/ActuatorMetricService.java (61%) rename {spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric => spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service}/CustomActuatorMetricService.java (68%) create mode 100644 spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/InMemoryMetricService.java create mode 100644 spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/MetricService.java create mode 100644 spring-boot-modules/spring-boot-actuator/src/main/resources/application-metrics.properties rename {spring-web-modules/spring-rest-testing => spring-boot-modules/spring-boot-actuator}/src/main/webapp/WEB-INF/api-servlet.xml (100%) rename {spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view => spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics}/graph.jsp (94%) rename {spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view => spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics}/homepage.jsp (100%) rename {spring-web-modules/spring-rest-testing => spring-boot-modules/spring-boot-actuator}/src/main/webapp/WEB-INF/web.xml (92%) create mode 100644 spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/metrics/MetricsApplicationIntegrationTest.java delete mode 100644 spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/controller/RootController.java delete mode 100644 spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/IActuatorMetricService.java delete mode 100644 spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/ICustomActuatorMetricService.java delete mode 100644 spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/IMetricService.java delete mode 100644 spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/MetricService.java diff --git a/spring-boot-modules/spring-boot-actuator/README.md b/spring-boot-modules/spring-boot-actuator/README.md index 59f7e929da..ea43377ed2 100644 --- a/spring-boot-modules/spring-boot-actuator/README.md +++ b/spring-boot-modules/spring-boot-actuator/README.md @@ -12,3 +12,4 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring - [Health Indicators in Spring Boot](https://www.baeldung.com/spring-boot-health-indicators) - [How to Enable All Endpoints in Spring Boot Actuator](https://www.baeldung.com/spring-boot-actuator-enable-endpoints) - [Spring Boot Startup Actuator Endpoint](https://www.baeldung.com/spring-boot-actuator-startup) +- [Metrics for your Spring REST API](https://www.baeldung.com/spring-rest-api-metrics) diff --git a/spring-boot-modules/spring-boot-actuator/pom.xml b/spring-boot-modules/spring-boot-actuator/pom.xml index b2c7a4d28e..1ccf436bbf 100644 --- a/spring-boot-modules/spring-boot-actuator/pom.xml +++ b/spring-boot-modules/spring-boot-actuator/pom.xml @@ -23,6 +23,10 @@ org.springframework.boot spring-boot-starter-web + + org.apache.tomcat.embed + tomcat-embed-jasper + org.springframework.boot spring-boot-starter-data-jpa @@ -35,6 +39,16 @@ com.h2database h2 + + javax.servlet + javax.servlet-api + provided + + + javax.servlet + jstl + runtime + org.springframework.boot spring-boot-starter-test @@ -51,6 +65,10 @@ spring-security-test test + + org.awaitility + awaitility + diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java new file mode 100644 index 0000000000..729b3c0b96 --- /dev/null +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/MetricsApplication.java @@ -0,0 +1,42 @@ +package com.baeldung.metrics; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.security.servlet.SecurityRequestMatchersManagementContextConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.context.request.RequestContextListener; + +import javax.servlet.ServletContext; + +@EnableScheduling +@ComponentScan("com.baeldung.metrics") +@SpringBootApplication +public class MetricsApplication extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(MetricsApplication.class); + } + + @Override + public void onStartup(ServletContext sc) { + // Manages the lifecycle of the root application context + sc.addListener(new RequestContextListener()); + } + + public static void main(final String[] args) { + // only load properties for this application + System.setProperty("spring.config.location", "classpath:application-metrics.properties"); + SpringApplication.run(MetricsApplication.class, args); + } + +} \ No newline at end of file diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring/WebConfig.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/WebConfig.java similarity index 81% rename from spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring/WebConfig.java rename to spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/WebConfig.java index e35acb0bf0..4c38e4dbad 100644 --- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring/WebConfig.java +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/WebConfig.java @@ -1,4 +1,4 @@ -package com.baeldung.spring; +package com.baeldung.metrics; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -10,14 +10,10 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration -@ComponentScan("com.baeldung.web") +@ComponentScan("com.baeldung.metrics") @EnableWebMvc public class WebConfig implements WebMvcConfigurer { - public WebConfig() { - super(); - } - @Bean public ViewResolver viewResolver() { final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); @@ -26,11 +22,10 @@ public class WebConfig implements WebMvcConfigurer { return viewResolver; } - // API @Override public void addViewControllers(final ViewControllerRegistry registry) { - registry.addViewController("/graph.html"); - registry.addViewController("/homepage.html"); + registry.addViewController("/metrics/graph.html"); + registry.addViewController("/metrics/homepage.html"); } } \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/controller/MetricsController.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/controller/MetricsController.java new file mode 100644 index 0000000000..e52ddd70f1 --- /dev/null +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/controller/MetricsController.java @@ -0,0 +1,41 @@ +package com.baeldung.metrics.controller; + +import com.baeldung.metrics.service.InMemoryMetricService; +import com.baeldung.metrics.service.MetricService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.Map; + +@Controller +@RequestMapping(value = "/metrics") +@ResponseBody +public class MetricsController { + + @Autowired + private InMemoryMetricService metricService; + + // change the qualifier to use the in-memory implementation + @Autowired + @Qualifier("customActuatorMetricService") + private MetricService graphMetricService; + + @GetMapping(value = "/metric") + public Map> getMetric() { + return metricService.getFullMetric(); + } + + @GetMapping(value = "/status-metric") + public Map getStatusMetric() { + return metricService.getStatusMetric(); + } + + @GetMapping(value = "/metric-graph-data") + public Object[][] getMetricData() { + return graphMetricService.getGraphData(); + } +} diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/MetricFilter.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter/MetricFilter.java similarity index 64% rename from spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/MetricFilter.java rename to spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter/MetricFilter.java index dee63b226f..0f7579f060 100644 --- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/MetricFilter.java +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/filter/MetricFilter.java @@ -1,4 +1,11 @@ -package com.baeldung.web.metric; +package com.baeldung.metrics.filter; + +import com.baeldung.metrics.service.CustomActuatorMetricService; +import com.baeldung.metrics.service.InMemoryMetricService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -9,24 +16,23 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.context.support.WebApplicationContextUtils; - @Component public class MetricFilter implements Filter { @Autowired - private IMetricService metricService; + private InMemoryMetricService metricService; @Autowired - private ICustomActuatorMetricService actMetricService; + private CustomActuatorMetricService actMetricService; @Override - public void init(final FilterConfig config) throws ServletException { + public void init(final FilterConfig config) { if (metricService == null || actMetricService == null) { - metricService = (IMetricService) WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext()).getBean("metricService"); - actMetricService = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext()).getBean(CustomActuatorMetricService.class); + WebApplicationContext appContext = WebApplicationContextUtils + .getRequiredWebApplicationContext(config.getServletContext()); + + metricService = appContext.getBean(InMemoryMetricService.class); + actMetricService = appContext.getBean(CustomActuatorMetricService.class); } } @@ -42,8 +48,4 @@ public class MetricFilter implements Filter { actMetricService.increaseCount(status); } - @Override - public void destroy() { - - } } diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/ActuatorMetricService.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/ActuatorMetricService.java similarity index 61% rename from spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/ActuatorMetricService.java rename to spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/ActuatorMetricService.java index 8c26fa04a0..3eef265c02 100644 --- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/ActuatorMetricService.java +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/ActuatorMetricService.java @@ -1,32 +1,31 @@ -package com.baeldung.web.metric; +package com.baeldung.metrics.service; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; - -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.Meter; -import io.micrometer.core.instrument.MeterRegistry; - @Service -public class ActuatorMetricService implements IActuatorMetricService { +public class ActuatorMetricService implements MetricService { + + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); @Autowired private MeterRegistry publicMetrics; - private final List> statusMetricsByMinute; + private final List> statusMetricsByMinute; private final List statusList; - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); public ActuatorMetricService() { - super(); - statusMetricsByMinute = new ArrayList>(); - statusList = new ArrayList(); + statusMetricsByMinute = new ArrayList<>(); + statusList = new ArrayList<>(); } @Override @@ -36,19 +35,19 @@ public class ActuatorMetricService implements IActuatorMetricService { final int rowCount = statusMetricsByMinute.size() + 1; final Object[][] result = new Object[rowCount][colCount]; result[0][0] = "Time"; - int j = 1; + int j = 1; for (final String status : statusList) { result[0][j] = status; j++; } for (int i = 1; i < rowCount; i++) { - result[i][0] = dateFormat.format(new Date(current.getTime() - (60000 * (rowCount - i)))); + result[i][0] = DATE_FORMAT.format(new Date(current.getTime() - (60000L * (rowCount - i)))); } List minuteOfStatuses; - List last = new ArrayList(); + List last = new ArrayList<>(); for (int i = 1; i < rowCount; i++) { minuteOfStatuses = statusMetricsByMinute.get(i - 1); @@ -64,11 +63,9 @@ public class ActuatorMetricService implements IActuatorMetricService { return result; } - // Non - API - - @Scheduled(fixedDelay = 60000) + @Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds:60000}") private void exportMetrics() { - final ArrayList lastMinuteStatuses = initializeStatuses(statusList.size()); + final List lastMinuteStatuses = initializeStatuses(statusList.size()); for (final Meter counterMetric : publicMetrics.getMeters()) { updateMetrics(counterMetric, lastMinuteStatuses); @@ -77,34 +74,32 @@ public class ActuatorMetricService implements IActuatorMetricService { statusMetricsByMinute.add(lastMinuteStatuses); } - private ArrayList initializeStatuses(final int size) { - final ArrayList counterList = new ArrayList(); + private List initializeStatuses(int size) { + List counterList = new ArrayList<>(); for (int i = 0; i < size; i++) { counterList.add(0); } return counterList; } - private void updateMetrics(final Meter counterMetric, final ArrayList statusCount) { - String status = ""; - int index = -1; - int oldCount = 0; + private void updateMetrics(Meter counterMetric, List statusCount) { - if (counterMetric.getId().getName().contains("counter.status.")) { - status = counterMetric.getId().getName().substring(15, 18); // example 404, 200 + String metricName = counterMetric.getId().getName(); + + if (metricName.contains("counter.status.")) { + // example 404, 200 + String status = metricName.substring(15, 18); appendStatusIfNotExist(status, statusCount); - index = statusList.indexOf(status); - oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index); + int index = statusList.indexOf(status); + int oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index); statusCount.set(index, (int)((Counter) counterMetric).count() + oldCount); } } - private void appendStatusIfNotExist(final String status, final ArrayList statusCount) { + private void appendStatusIfNotExist(String status, List statusCount) { if (!statusList.contains(status)) { statusList.add(status); statusCount.add(0); } } - - // -} \ No newline at end of file +} diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/CustomActuatorMetricService.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/CustomActuatorMetricService.java similarity index 68% rename from spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/CustomActuatorMetricService.java rename to spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/CustomActuatorMetricService.java index ee17825b7c..9b4ccaa875 100644 --- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/CustomActuatorMetricService.java +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/CustomActuatorMetricService.java @@ -1,40 +1,36 @@ -package com.baeldung.web.metric; +package com.baeldung.metrics.service; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.search.Search; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; - -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.search.Search; - @Service -public class CustomActuatorMetricService implements ICustomActuatorMetricService { +public class CustomActuatorMetricService implements MetricService { + + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); @Autowired private MeterRegistry registry; - private final List> statusMetricsByMinute; + private final List> statusMetricsByMinute; private final List statusList; - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); public CustomActuatorMetricService() { - super(); - statusMetricsByMinute = new ArrayList>(); - statusList = new ArrayList(); + statusMetricsByMinute = new ArrayList<>(); + statusList = new ArrayList<>(); } - // API - - @Override - public void increaseCount(final int status) { + public void increaseCount(int status) { String counterName = "counter.status." + status; - registry.counter(counterName).increment(1); + registry.counter(counterName).increment(); if (!statusList.contains(counterName)) { statusList.add(counterName); } @@ -55,7 +51,7 @@ public class CustomActuatorMetricService implements ICustomActuatorMetricService } for (int i = 1; i < rowCount; i++) { - result[i][0] = dateFormat.format(new Date(current.getTime() - (60000 * (rowCount - i)))); + result[i][0] = DATE_FORMAT.format(new Date(current.getTime() - (60000L * (rowCount - i)))); } List minuteOfStatuses; @@ -72,19 +68,17 @@ public class CustomActuatorMetricService implements ICustomActuatorMetricService return result; } - // Non - API - - @Scheduled(fixedDelay = 60000) + @Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds:60000}") private void exportMetrics() { - final ArrayList statusCount = new ArrayList(); + List statusCount = new ArrayList<>(); for (final String status : statusList) { Search search = registry.find(status); - if (search != null) { - Counter counter = search.counter(); - statusCount.add(counter != null ? ((int) counter.count()) : 0); - registry.remove(counter); - } else { + Counter counter = search.counter(); + if (counter == null) { statusCount.add(0); + } else { + statusCount.add((int) counter.count()); + registry.remove(counter); } } statusMetricsByMinute.add(statusCount); diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/InMemoryMetricService.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/InMemoryMetricService.java new file mode 100644 index 0000000000..0be5c21727 --- /dev/null +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/InMemoryMetricService.java @@ -0,0 +1,112 @@ +package com.baeldung.metrics.service; + +import org.springframework.stereotype.Service; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class InMemoryMetricService implements MetricService { + + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + private final Map> metricMap; + private final Map statusMetric; + private final Map> timeMap; + + public InMemoryMetricService() { + metricMap = new ConcurrentHashMap<>(); + statusMetric = new ConcurrentHashMap<>(); + timeMap = new ConcurrentHashMap<>(); + } + + public void increaseCount(String request, int status) { + increaseMainMetric(request, status); + increaseStatusMetric(status); + updateTimeMap(status); + } + + public Map> getFullMetric() { + return metricMap; + } + + public Map getStatusMetric() { + return statusMetric; + } + + public Object[][] getGraphData() { + final int colCount = statusMetric.keySet().size() + 1; + final Set allStatus = statusMetric.keySet(); + final int rowCount = timeMap.keySet().size() + 1; + + final Object[][] result = new Object[rowCount][colCount]; + result[0][0] = "Time"; + + int j = 1; + for (final int status : allStatus) { + result[0][j] = status; + j++; + } + int i = 1; + Map tempMap; + for (final Entry> entry : timeMap.entrySet()) { + result[i][0] = entry.getKey(); + tempMap = entry.getValue(); + for (j = 1; j < colCount; j++) { + result[i][j] = tempMap.get((Integer) result[0][j]); + if (result[i][j] == null) { + result[i][j] = 0; + } + } + i++; + } + + for (int k = 1; k < result[0].length; k++) { + result[0][k] = result[0][k].toString(); + } + + return result; + } + + private void increaseMainMetric(String request, int status) { + Map statusMap = metricMap.get(request); + if (statusMap == null) { + statusMap = new ConcurrentHashMap<>(); + } + + Integer count = statusMap.get(status); + if (count == null) { + count = 1; + } else { + count++; + } + statusMap.put(status, count); + metricMap.put(request, statusMap); + } + + private void increaseStatusMetric(int status) { + statusMetric.merge(status, 1, Integer::sum); + } + + private void updateTimeMap(int status) { + final String time = DATE_FORMAT.format(new Date()); + Map statusMap = timeMap.get(time); + if (statusMap == null) { + statusMap = new ConcurrentHashMap<>(); + } + + Integer count = statusMap.get(status); + if (count == null) { + count = 1; + } else { + count++; + } + statusMap.put(status, count); + timeMap.put(time, statusMap); + } + +} diff --git a/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/MetricService.java b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/MetricService.java new file mode 100644 index 0000000000..3642102b67 --- /dev/null +++ b/spring-boot-modules/spring-boot-actuator/src/main/java/com/baeldung/metrics/service/MetricService.java @@ -0,0 +1,7 @@ +package com.baeldung.metrics.service; + +public interface MetricService { + + Object[][] getGraphData(); + +} diff --git a/spring-boot-modules/spring-boot-actuator/src/main/resources/application-metrics.properties b/spring-boot-modules/spring-boot-actuator/src/main/resources/application-metrics.properties new file mode 100644 index 0000000000..5f753a0c62 --- /dev/null +++ b/spring-boot-modules/spring-boot-actuator/src/main/resources/application-metrics.properties @@ -0,0 +1,9 @@ +management.endpoints.web.exposure.include=info,health,metrics + +# JPA and Security is not required for Metrics application +spring.autoconfigure.exclude= org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, \ + org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, \ + org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, \ + org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, \ + org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration, \ + org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration diff --git a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/api-servlet.xml b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/api-servlet.xml similarity index 100% rename from spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/api-servlet.xml rename to spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/api-servlet.xml diff --git a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view/graph.jsp b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics/graph.jsp similarity index 94% rename from spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view/graph.jsp rename to spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics/graph.jsp index e1d5fdc987..75976557a0 100644 --- a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view/graph.jsp +++ b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics/graph.jsp @@ -11,7 +11,7 @@ }); function drawChart() { - $.get("", + $.get("", function(mydata) { var data = google.visualization.arrayToDataTable(mydata); diff --git a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view/homepage.jsp b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics/homepage.jsp similarity index 100% rename from spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/view/homepage.jsp rename to spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/view/metrics/homepage.jsp diff --git a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/web.xml b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/web.xml similarity index 92% rename from spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/web.xml rename to spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/web.xml index 7129b6b4af..84566ba93d 100644 --- a/spring-web-modules/spring-rest-testing/src/main/webapp/WEB-INF/web.xml +++ b/spring-boot-modules/spring-boot-actuator/src/main/webapp/WEB-INF/web.xml @@ -16,7 +16,7 @@ contextConfigLocation - com.baeldung.spring + com.baeldung.metrics @@ -37,7 +37,7 @@ metricFilter - com.baeldung.web.metric.MetricFilter + com.baeldung.metrics.filter.MetricFilter diff --git a/spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/metrics/MetricsApplicationIntegrationTest.java b/spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/metrics/MetricsApplicationIntegrationTest.java new file mode 100644 index 0000000000..c83d4625dc --- /dev/null +++ b/spring-boot-modules/spring-boot-actuator/src/test/java/com/baeldung/metrics/MetricsApplicationIntegrationTest.java @@ -0,0 +1,64 @@ +package com.baeldung.metrics; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.test.context.ActiveProfiles; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@SpringBootTest( + classes = MetricsApplication.class, + webEnvironment = RANDOM_PORT, + properties = {"fixedDelay.in.milliseconds=2000"} +) +@ActiveProfiles("metrics") +class MetricsApplicationIntegrationTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void givenStatuses_WhenScheduledMethodExecuted_ExpectCountsAreAggregated() { + restTemplate.getForObject("/metrics/metric/notFound", String.class); + restTemplate.getForObject("/metrics/metric", String.class); + + await().untilAsserted(() -> { + Object[][] statusCounts = restTemplate.getForObject("/metrics/metric-graph-data", Object[][].class); + + assertThat(statusCounts[0]).contains("counter.status.200", "counter.status.404"); + + List requestCounts = getRequestCounts(statusCounts); + verify404RequestFrom(requestCounts); + verify200RequestsFrom(requestCounts); + }); + } + + private static void verify200RequestsFrom(List requestCounts) { + assertThat(requestCounts.size()).isGreaterThan(1); + } + + private static void verify404RequestFrom(List requestCounts) { + assertThat(requestCounts).contains(1); + } + + private static List getRequestCounts(Object[][] statusCounts) { + List requestCounts = new ArrayList<>(); + for (int i = 1; i < statusCounts.length; i++) { + for (int j = 1; j < statusCounts[i].length; j++) { + Integer count = (Integer) statusCounts[i][j]; + if (count >= 1) { + requestCounts.add(count); + } + } + } + return requestCounts; + } + +} diff --git a/spring-web-modules/spring-rest-testing/README.md b/spring-web-modules/spring-rest-testing/README.md index 2a77b5dded..806e67b7ec 100644 --- a/spring-web-modules/spring-rest-testing/README.md +++ b/spring-web-modules/spring-rest-testing/README.md @@ -11,7 +11,6 @@ The "Learn Spring Security" Classes: http://github.learnspringsecurity.com ### Relevant Articles: - [Integration Testing with the Maven Cargo plugin](https://www.baeldung.com/integration-testing-with-the-maven-cargo-plugin) -- [Metrics for your Spring REST API](https://www.baeldung.com/spring-rest-api-metrics) - [Testing Exceptions with Spring MockMvc](https://www.baeldung.com/spring-mvc-test-exceptions) ### Build the Project diff --git a/spring-web-modules/spring-rest-testing/pom.xml b/spring-web-modules/spring-rest-testing/pom.xml index 52c1f6418e..1390898bf9 100644 --- a/spring-web-modules/spring-rest-testing/pom.xml +++ b/spring-web-modules/spring-rest-testing/pom.xml @@ -21,19 +21,10 @@ org.springframework.boot spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-actuator - org.aspectj aspectjweaver - - org.apache.tomcat.embed - tomcat-embed-jasper - provided - org.springframework @@ -69,14 +60,6 @@ org.springframework spring-expression - - org.springframework - spring-web - - - org.springframework - spring-webmvc - org.springframework.data spring-data-commons @@ -137,17 +120,6 @@ net.bytebuddy byte-buddy - - - javax.servlet - javax.servlet-api - provided - - - javax.servlet - jstl - runtime - com.fasterxml.jackson.core diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring/Application.java b/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring/Application.java index c35c4d7e5e..78c9b88ddb 100644 --- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring/Application.java +++ b/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/spring/Application.java @@ -1,38 +1,19 @@ package com.baeldung.spring; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; - import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.ComponentScan; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.web.context.request.RequestContextListener; /** * Main Application Class - uses Spring Boot. Just run this as a normal Java * class to run up a Jetty Server (on http://localhost:8082/spring-rest-full) * */ -@EnableScheduling @EnableAutoConfiguration @ComponentScan("com.baeldung") @SpringBootApplication -public class Application extends SpringBootServletInitializer { - - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { - return application.sources(Application.class); - } - - @Override - public void onStartup(ServletContext sc) throws ServletException { - // Manages the lifecycle of the root application context - sc.addListener(new RequestContextListener()); - } +public class Application { public static void main(final String[] args) { SpringApplication.run(Application.class, args); diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/controller/RootController.java b/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/controller/RootController.java deleted file mode 100644 index 005f6f023b..0000000000 --- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/controller/RootController.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.baeldung.web.controller; - -import java.util.Map; - -import com.baeldung.web.metric.IActuatorMetricService; -import com.baeldung.web.metric.IMetricService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -@RequestMapping(value = "/auth/") -public class RootController { - - @Autowired - private IMetricService metricService; - - @Autowired - private IActuatorMetricService actMetricService; - - public RootController() { - super(); - } - - // API - - @RequestMapping(value = "/metric", method = RequestMethod.GET) - @ResponseBody - public Map getMetric() { - return metricService.getFullMetric(); - } - - @RequestMapping(value = "/status-metric", method = RequestMethod.GET) - @ResponseBody - public Map getStatusMetric() { - return metricService.getStatusMetric(); - } - - @RequestMapping(value = "/metric-graph", method = RequestMethod.GET) - @ResponseBody - public Object[][] drawMetric() { - final Object[][] result = metricService.getGraphData(); - for (int i = 1; i < result[0].length; i++) { - result[0][i] = result[0][i].toString(); - } - return result; - } - - -} diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/IActuatorMetricService.java b/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/IActuatorMetricService.java deleted file mode 100644 index 60bb43ee00..0000000000 --- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/IActuatorMetricService.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.baeldung.web.metric; - -public interface IActuatorMetricService { - Object[][] getGraphData(); -} diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/ICustomActuatorMetricService.java b/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/ICustomActuatorMetricService.java deleted file mode 100644 index 5126252e27..0000000000 --- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/ICustomActuatorMetricService.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.baeldung.web.metric; - -public interface ICustomActuatorMetricService { - - void increaseCount(final int status); - - Object[][] getGraphData(); -} diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/IMetricService.java b/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/IMetricService.java deleted file mode 100644 index b8dfa60215..0000000000 --- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/IMetricService.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.baeldung.web.metric; - -import java.util.Map; - -public interface IMetricService { - - void increaseCount(final String request, final int status); - - Map getFullMetric(); - - Map getStatusMetric(); - - Object[][] getGraphData(); -} diff --git a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/MetricService.java b/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/MetricService.java deleted file mode 100644 index fd3cccab3e..0000000000 --- a/spring-web-modules/spring-rest-testing/src/main/java/com/baeldung/web/metric/MetricService.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.baeldung.web.metric; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.springframework.stereotype.Service; - -@Service -public class MetricService implements IMetricService { - - private ConcurrentMap> metricMap; - private ConcurrentMap statusMetric; - private ConcurrentMap> timeMap; - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - - public MetricService() { - super(); - metricMap = new ConcurrentHashMap>(); - statusMetric = new ConcurrentHashMap(); - timeMap = new ConcurrentHashMap>(); - } - - // API - - @Override - public void increaseCount(final String request, final int status) { - increaseMainMetric(request, status); - increaseStatusMetric(status); - updateTimeMap(status); - } - - @Override - public Map getFullMetric() { - return metricMap; - } - - @Override - public Map getStatusMetric() { - return statusMetric; - } - - @Override - public Object[][] getGraphData() { - final int colCount = statusMetric.keySet().size() + 1; - final Set allStatus = statusMetric.keySet(); - final int rowCount = timeMap.keySet().size() + 1; - - final Object[][] result = new Object[rowCount][colCount]; - result[0][0] = "Time"; - - int j = 1; - for (final int status : allStatus) { - result[0][j] = status; - j++; - } - int i = 1; - ConcurrentMap tempMap; - for (final Entry> entry : timeMap.entrySet()) { - result[i][0] = entry.getKey(); - tempMap = entry.getValue(); - for (j = 1; j < colCount; j++) { - result[i][j] = tempMap.get(result[0][j]); - if (result[i][j] == null) { - result[i][j] = 0; - } - } - i++; - } - - return result; - } - - // NON-API - - private void increaseMainMetric(final String request, final int status) { - ConcurrentHashMap statusMap = metricMap.get(request); - if (statusMap == null) { - statusMap = new ConcurrentHashMap(); - } - - Integer count = statusMap.get(status); - if (count == null) { - count = 1; - } else { - count++; - } - statusMap.put(status, count); - metricMap.put(request, statusMap); - } - - private void increaseStatusMetric(final int status) { - final Integer statusCount = statusMetric.get(status); - if (statusCount == null) { - statusMetric.put(status, 1); - } else { - statusMetric.put(status, statusCount + 1); - } - } - - private void updateTimeMap(final int status) { - final String time = dateFormat.format(new Date()); - ConcurrentHashMap statusMap = timeMap.get(time); - if (statusMap == null) { - statusMap = new ConcurrentHashMap(); - } - - Integer count = statusMap.get(status); - if (count == null) { - count = 1; - } else { - count++; - } - statusMap.put(status, count); - timeMap.put(time, statusMap); - } - -} diff --git a/spring-web-modules/spring-rest-testing/src/main/resources/application.properties b/spring-web-modules/spring-rest-testing/src/main/resources/application.properties index 52d93b4cff..cb9d72e41f 100644 --- a/spring-web-modules/spring-rest-testing/src/main/resources/application.properties +++ b/spring-web-modules/spring-rest-testing/src/main/resources/application.properties @@ -1,3 +1,2 @@ server.port=8082 server.servlet.context-path=/spring-rest-full -endpoints.metrics.enabled=true \ No newline at end of file