One‑Line Annotation for Method Monitoring in Spring Boot
This article demonstrates how to add a single @Monitor annotation to Spring Boot 3.5.0 services and automatically obtain call‑trace and execution‑time metrics by leveraging Spring AOP, SimpleTraceInterceptor, PerformanceMonitorInterceptor, custom advisors, and a BeanPostProcessor, with full code examples and a test controller.
Environment: Spring Boot 3.5.0.
1. Introduction In Spring Boot projects, method‑level call tracing and execution‑time monitoring can be achieved with low‑intrusion by using Spring AOP together with built‑in interceptors. A custom annotation drives the process, allowing developers to toggle tracing and performance monitoring without writing repetitive code.
2. Practical example
2.1 Custom annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Monitor {
// trace method call
boolean trace() default true;
// record execution time
boolean monitor() default true;
}Any class or method annotated with @Monitor will be automatically proxied; the trace and monitor attributes control whether call tracing or performance statistics are collected.
2.2 Define abstract advisor for tracing
public class TraceAdvisor implements PointcutAdvisor {
public Advice getAdvice() {
return new SimpleTraceInterceptor(true);
}
public Pointcut getPointcut() {
return new Pointcut() {
public MethodMatcher getMethodMatcher() {
return new AbstractMethodMatcher() {
public boolean matches(Method method, Class<?> targetClass) {
Monitor annotation = method.getAnnotation(Monitor.class);
return annotation != null && annotation.trace();
}
};
}
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
};
}
}2.3 Define advisor for performance monitoring
public class MonitorAdvisor implements PointcutAdvisor {
public Advice getAdvice() {
return new PerformanceMonitorInterceptor(true);
}
public Pointcut getPointcut() {
return new Pointcut() {
public MethodMatcher getMethodMatcher() {
return new AbstractMethodMatcher() {
public boolean matches(Method method, Class<?> targetClass) {
Monitor annotation = method.getAnnotation(Monitor.class);
return annotation != null && annotation.monitor();
}
};
}
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
};
}
}2.4 Abstract method matcher (no parameter matching needed)
public abstract class AbstractMethodMatcher implements MethodMatcher {
@Override
public boolean isRuntime() { return false; }
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) { return false; }
}2.5 BeanPostProcessor to attach advisors
@Component
public class MonitorBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> sourceClass = AopProxyUtils.ultimateTargetClass(bean);
boolean exists = sourceClass.isAnnotationPresent(Monitor.class);
if (!exists) {
for (Method m : sourceClass.getDeclaredMethods()) {
if (m.isAnnotationPresent(Monitor.class)) { exists = true; break; }
}
}
if (!exists) return bean;
TraceAdvisor traceAdvisor = new TraceAdvisor();
MonitorAdvisor monitorAdvisor = new MonitorAdvisor();
if (AopUtils.isAopProxy(bean) && bean instanceof Advised advised) {
advised.addAdvisor(traceAdvisor);
advised.addAdvisor(monitorAdvisor);
} else {
ProxyFactory factory = new ProxyFactory(bean);
factory.setExposeProxy(true);
factory.addAdvisor(traceAdvisor);
factory.addAdvisor(monitorAdvisor);
bean = factory.getProxy();
}
return bean;
}
}2.6 Test controller
@RestController
@RequestMapping("/monitors")
public class MonitorController {
@GetMapping("/1")
@Monitor
public Object query() throws Exception {
TimeUnit.MILLISECONDS.sleep(new Random().nextLong(1000));
return "query...";
}
@GetMapping("/2")
@Monitor(trace = false)
public Object save() throws Exception {
TimeUnit.MILLISECONDS.sleep(new Random().nextLong(1000));
return "save...";
}
}Running the two endpoints prints trace and performance information to the console, as shown in the accompanying screenshot.
Thus, with a single @Monitor annotation developers obtain unified call‑trace and performance statistics, simplifying debugging and operational monitoring.
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.
