javaadvanced

Spring Boot AOP — Aspects and Advice

Use Spring AOP for cross-cutting concerns: logging, timing, auditing, and transaction-like behavior.

java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.*;
import java.util.Arrays;

// Custom annotation to mark methods for timing
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {
    String value() default "";
}

// Logging aspect
@Aspect
@Component
class LoggingAspect {
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);

    // Before any controller method
    @Before("execution(* com.example.controller.*.*(..))")
    public void logBefore(org.aspectj.lang.JoinPoint jp) {
        log.info("→ {}.{}({})",
            jp.getTarget().getClass().getSimpleName(),
            jp.getSignature().getName(),
            Arrays.toString(jp.getArgs()));
    }

    // After returning
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void logAfter(org.aspectj.lang.JoinPoint jp, Object result) {
        log.info("← {}.{} returned: {}",
            jp.getTarget().getClass().getSimpleName(),
            jp.getSignature().getName(),
            result);
    }

    // After throwing
    @AfterThrowing(pointcut = "execution(* com.example..*.*(..))", throwing = "ex")
    public void logException(org.aspectj.lang.JoinPoint jp, Exception ex) {
        log.error("✗ {}.{} threw: {}",
            jp.getTarget().getClass().getSimpleName(),
            jp.getSignature().getName(),
            ex.getMessage());
    }
}

// Timing aspect
@Aspect
@Component
class TimingAspect {
    private static final Logger log = LoggerFactory.getLogger(TimingAspect.class);

    @Around("@annotation(timed)")
    public Object measureTime(ProceedingJoinPoint pjp, Timed timed) throws Throwable {
        String label = timed.value().isEmpty()
            ? pjp.getSignature().getName() : timed.value();

        long start = System.nanoTime();
        try {
            return pjp.proceed();
        } finally {
            long ms = (System.nanoTime() - start) / 1_000_000;
            log.info("[TIMER] {} took {}ms", label, ms);
        }
    }
}

// Usage in service
@Component
class OrderService {
    @Timed("process-order")
    public String processOrder(String orderId) {
        // Business logic
        return "Processed: " + orderId;
    }
}

Use Cases

  • Automatic method logging across services
  • Performance monitoring with timing aspects
  • Audit trail for sensitive operations

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.