The full picture
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)
public @interface Timed { String value(); }
@Aspect @Component
public class TimingAspect {
private final MeterRegistry metrics;
TimingAspect(MeterRegistry metrics) { this.metrics = metrics; }
@Around("@annotation(timed)") // binds the annotation instance
public Object time(ProceedingJoinPoint pjp, Timed timed) throws Throwable {
long t0 = System.nanoTime();
String outcome = "success";
try {
return pjp.proceed(); // 1. ALWAYS return proceed()'s value
} catch (Throwable t) {
outcome = "error"; // 2. observe, DON'T swallow
throw t;
} finally { // 3. aspect never throws from here
metrics.timer(timed.value(), "outcome", outcome)
.record(System.nanoTime() - t0, TimeUnit.NANOSECONDS);
}
}
}- Pointcut vocabulary:
execution(pattern)method matching;within(type)lexical scope;@annotation/@withinannotation-driven;bean(name)Spring-specific; combine with&& || !. Reusable via@Pointcutmethods. - Advice types:
@Before,@AfterReturning,@AfterThrowing,@After(finally),@Around— use the least powerful that works; @Around everywhere is a smell. - Mistake checklist: forgetting
proceed()(target never runs), returning null from Object-typed advice on primitive-returning methods (NPE on unboxing), doing blocking I/O in a hot-path aspect, wide pointcuts catching Spring's own infrastructure beans.