PrepZone Logo
PrepZone

AOP: Advice, Pointcuts, Aspects

Cross-cutting concerns with @Aspect, @Before, @After and @Around advice.

Why this matters

  • Copy-pasting logging or timing code into every service method violates DRY and clutters business logic.
  • Spring's @Transactional and @Cacheable are themselves AOP proxies — understanding AOP explains how they work.
  • Custom aspects handle BookStore-specific concerns like audit trails and performance monitoring.

Enable AOP

Java
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Spring Boot auto-configures AOP when the starter is present.

A logging aspect

Java
@Aspect
@Component
public class LoggingAspect {
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);

    @Before("execution(* com.example.bookstore.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        log.info("Calling {}.{}()",
            joinPoint.getTarget().getClass().getSimpleName(),
            joinPoint.getSignature().getName());
    }

    @AfterReturning(pointcut = "execution(* com.example.bookstore.service.*.*(..))",
                    returning = "result")
    public void logAfter(JoinPoint joinPoint, Object result) {
        log.debug("{}.{}() returned {}",
            joinPoint.getTarget().getClass().getSimpleName(),
            joinPoint.getSignature().getName(),
            result);
    }
}

Every BookService, OrderService, etc. method is logged without touching their code.

Proxy
@Before adviceLogging, security check
Target bean
bookService.findById()Your business method
@After adviceMetrics, cleanup
Spring creates a proxy around your bean. Advice runs before and after the real method.

Advice types

  • @Before — Runs before the method. Logging, argument validation.
  • @AfterReturning — Runs after successful return. Result logging, metrics.
  • @AfterThrowing — Runs when the method throws. Error logging, alerting.
  • @After — Runs after method completes (success or failure). Cleanup.
  • @Around — Wraps the method entirely. Timing, retry, transaction control.

@Around for execution timing

Java
@Aspect
@Component
public class PerformanceAspect {
    private static final Logger log = LoggerFactory.getLogger(PerformanceAspect.class);

    @Around("execution(* com.example.bookstore.service.*.*(..))")
    public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            long elapsed = System.currentTimeMillis() - start;
            if (elapsed > 500) {
                log.warn("SLOW: {}.{}() took {}ms",
                    joinPoint.getTarget().getClass().getSimpleName(),
                    joinPoint.getSignature().getName(), elapsed);
            }
        }
    }
}

joinPoint.proceed() invokes the actual method. Code before proceed() is "before" advice; code in finally runs regardless of outcome.

Pointcut expressions

Target specific methods with precision:

Java
// All methods in the service package
@Pointcut("execution(* com.example.bookstore.service.*.*(..))")
public void serviceLayer() {}

// Only methods annotated with @Transactional
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}

// Methods taking a specific parameter type
@Pointcut("execution(* com.example.bookstore.service.BookService.create(..))")
public void bookCreation() {}

@Around("serviceLayer() && bookCreation()")
public Object auditBookCreation(ProceedingJoinPoint jp) throws Throwable { ... }

Combine pointcuts with &&, ||, and ! for precise targeting.

Custom annotation + aspect

Cleaner than package-based pointcuts:

Java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audited {
    String action();
}

@Aspect
@Component
public class AuditAspect {
    private final AuditService auditService;

    @AfterReturning("@annotation(audited)")
    public void recordAudit(JoinPoint jp, Audited audited) {
        auditService.log(audited.action(), jp.getArgs());
    }
}

Usage in BookStore:

Java
@Service
public class BookService {
    @Audited(action = "CREATE_BOOK")
    @Transactional
    public Book create(CreateBookRequest request) { ... }
}

How proxies work

Spring creates a proxy around your bean. External calls go through the proxy (which runs aspects), then delegate to the real object. Two proxy types:

Proxy types

  • JDK dynamic proxy — Interface-based. Works when the bean implements an interface.
  • CGLIB proxy — Subclass-based. Used for concrete classes without interfaces.

@Transactional on BookService (a concrete class) uses CGLIB.

AOP vs manual cross-cutting

ApproachProsCons
AOP aspectDRY, declarative, reusableHarder to debug, proxy overhead
Manual in each methodExplicit, easy to traceDuplicated, clutters business logic
Interceptor / FilterHTTP-level concernsCannot target service methods
  • AOP aspect

    ProsDRY, declarative, reusable
    ConsHarder to debug, proxy overhead
  • Manual in each method

    ProsExplicit, easy to trace
    ConsDuplicated, clutters business logic
  • Interceptor / Filter

    ProsHTTP-level concerns
    ConsCannot target service methods

Use AOP for service-layer cross-cutting. Use filters for HTTP-level concerns.

Quick recall

Everything you need if you only revisit this box.

  • AOP separates cross-cutting concerns (logging, timing, auditing) from business logic.
  • @Aspect classes define advice (@Before, @Around, etc.) applied via pointcut expressions.
  • @Around with joinPoint.proceed() wraps method execution for timing and retry logic.
  • Custom annotations (@Audited) paired with aspects are cleaner than package-based pointcuts.
  • Spring uses CGLIB proxies for concrete classes, JDK proxies for interfaces.
  • Self-invocation within a class bypasses the AOP proxy — extract to a separate bean.

Test yourself

Answer these before moving on — recall is what makes it stick.