Monitor Spring Boot API Latency with Actuator, AOP, and Prometheus
This tutorial shows how to instrument Spring Boot APIs using Actuator, a custom @Monitor annotation with AOP, and Prometheus to collect and visualize method execution times, providing a complete end‑to‑end monitoring solution.
1. Introduction
This article explains how to monitor arbitrary API call latency in a Spring Boot application by combining Spring Boot Actuator, a custom @Monitor annotation, AOP, and Prometheus.
2. Environment Setup
Required dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>Expose all actuator endpoints:
management:
endpoints:
web:
base-path: /ac
exposure:
include: '*'3. Practical Example
3.1 Define the @Monitor annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Monitor {
String[] tags() default {};
}3.2 Implement the monitoring aspect
@Component
@Aspect
public class MonitorAspect {
private final MeterRegistry meterRegistry;
private static final String API_TIMER_METER_NAME = "myapp.api.timer";
public MonitorAspect(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Pointcut("@annotation(monitor)")
private void pcMonitor(Monitor monitor) {}
@Around("pcMonitor(monitor)")
public Object around(ProceedingJoinPoint pjp, Monitor monitor) throws Throwable {
Timer.Sample sample = Timer.start(this.meterRegistry);
String[] tags = monitor.tags();
Object ret = null;
Throwable ex = null;
try {
ret = pjp.proceed();
} catch (Throwable th) {
ex = th;
throw th;
} finally {
List<String> listTags = new ArrayList<>();
listTags.addAll(Arrays.asList(tags));
if (Objects.nonNull(ex)) {
listTags.add(ex.getClass().getSimpleName());
}
Timer timer = meterRegistry.timer(API_TIMER_METER_NAME, listTags.toArray(new String[0]));
sample.stop(timer);
}
return ret;
}
}3.3 Service layer
@Service
public class UserService {
private static final List<User> DATAS = List.of(
new User(1L, "张三", "男", 22),
new User(2L, "李四", "男", 23),
new User(3L, "王五", "女", 22),
new User(4L, "赵六", "男", 32));
public List<User> queryUsers() {
sleep(2000);
return DATAS;
}
public User queryById(Long id) {
sleep(1000);
return DATAS.stream().filter(user -> user.getId() == id).findFirst().orElse(null);
}
private void sleep(int time) {
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(time));
} catch (InterruptedException e) {}
}
}3.4 Controller layer
@Monitor(tags = {"UserController", "list"})
@GetMapping("")
public List<User> list() {
return this.userService.queryUsers();
}
@Monitor(tags = {"UserController", "ById"})
@GetMapping("/{id}")
public User queryById(@PathVariable Long id) {
return this.userService.queryById(id);
}Note: The tags attribute must contain an even number of strings because they are paired as key/value tags.
4. Testing and Visualization
Invoke the two endpoints repeatedly and access /ac/metrics/myapp.api.timer to view raw metrics. Prometheus scrapes the /actuator/prometheus endpoint, and you can visualize latency trends in Grafana or the Prometheus UI.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
