1. Dependency Configuration
Include the Actuator depandency in your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2. Request Data Storage Implementation
Use a ConcurrentHashMap to store request metrics per URI (timestamp + response time):
@Component
public class RequestMetricsCollector {
private final ConcurrentHashMap<String, Queue<RequestMetric>> metricsMap = new ConcurrentHashMap<>();
private static class RequestMetric {
long requestTime;
long processingTime;
RequestMetric(long requestTime, long processingTime) {
this.requestTime = requestTime;
this.processingTime = processingTime;
}
}
public synchronized void logRequest(String uri, long processingTime) {
long currentTime = System.currentTimeMillis();
metricsMap.compute(uri, (key, existingQueue) -> {
if (existingQueue == null) {
existingQueue = new ConcurrentLinkedQueue<>();
}
existingQueue.add(new RequestMetric(currentTime, processingTime));
if (existingQueue.size() > 100000) {
existingQueue.poll();
}
removeOldEntries(existingQueue, currentTime);
return existingQueue;
});
}
private void removeOldEntries(Queue<RequestMetric> queue, long currentTime) {
while (!queue.isEmpty() && (currentTime - queue.peek().requestTime > 86400000)) {
queue.poll();
}
}
public Map<String, Map<String, Object>> computeMetrics() {
long timeThreshold = System.currentTimeMillis() - 86400000;
Map<String, Map<String, Object>> metricsResult = new HashMap<>();
metricsMap.forEach((uri, metrics) -> {
List<RequestMetric> recentMetrics = metrics.stream()
.filter(m -> m.requestTime >= timeThreshold)
.collect(Collectors.toList());
if (!recentMetrics.isEmpty()) {
int requestCount = recentMetrics.size();
long totalTime = recentMetrics.stream().mapToLong(m -> m.processingTime).sum();
long peakTime = recentMetrics.stream().mapToLong(m -> m.processingTime).max().orElse(0);
Map<String, Object> uriStats = new HashMap<>();
uriStats.put("requestCount", requestCount);
uriStats.put("averageTime", totalTime / requestCount);
uriStats.put("peakTime", peakTime);
metricsResult.put(uri, uriStats);
}
});
return metricsResult.entrySet().stream()
.sorted((entry1, entry2) ->
Integer.compare((Integer) entry2.getValue().get("requestCount"),
(Integer) entry1.getValue().get("requestCount")))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
}
}
3. AOP Aspect for Request Monitoring
@Aspect
@Component
public class RequestMonitoringAspect {
@Autowired
private RequestMetricsCollector metricsCollector;
@Around("execution(* com.example..*Controller.*(..))")
public Object monitorRequest(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest httpRequest = ((ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes()).getRequest();
String requestUri = httpRequest.getRequestURI();
long beginTime = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long elapsedTime = System.currentTimeMillis() - beginTime;
metricsCollector.logRequest(requestUri, elapsedTime);
}
}
}
4. Custom Actuator Endpoint
@Endpoint(id = "request-metrics")
@Component
public class RequestMetricsEndpoint {
@Autowired
private RequestMetricsCollector metricsCollector;
@ReadOperation
public Map<String, Object> retrieveMetrics() {
return new LinkedHashMap<String, Object>() {{
put("timestamp", System.currentTimeMillis());
put("metrics", metricsCollector.computeMetrics());
}};
}
}
Execute requests to your application endpoints (e.g., /api/users, /api/orders) to collect metrics automatically.
Accessing Statistics
Retrieve 24-hour metrics via custom endpoint:
GET /actuator/request-metrics
{
"timestamp": 1717250000000,
"metrics": {
"/api/users": {
"requestCount": 1500,
"averageTime": 45,
"peakTime": 1200
},
"/api/orders": {
"requestCount": 1200,
"averageTime": 80,
"peakTime": 2500
}
}
}
Scheduled Data Cleanup
Perform hourly cleanup of expired data:
@Scheduled(fixedRate = 3600000)
public void performCleanup() {
long currentMillis = System.currentTimeMillis();
metricsMap.forEach((uri, queue) ->
queue.removeIf(metric -> (currentMillis - metric.requestTime) > 86400000)
);
}
Security Configuration
Restrict endpoint access using Spring Security:
@Configuration
public class EndpointSecurityConfig {
@Bean
SecurityFilterChain securityConfig(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth ->
auth.requestMatchers("/actuator/request-metrics").hasRole("ADMIN")
);
return http.build();
}
}