Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › Spring › AOP and proxies

AOP: advice and pointcuts

Which methods does advice apply to, and in what order does it run?

When work that many methods need in the same way, such as logging, is written in one class (an aspect), Spring adds that code before and after the method calls. The code that is added is called advice, and the expression that selects which methods it is added to is called a pointcut.

Checked against real execution · All 72 combinations were run on Spring Framework 7.0.9 and checked against the results

Concepts in this lab

Aspect-oriented programming (AOP)
This programming technique separates additional behavior that appears repeatedly in many places (crosscutting concerns), such as logging, transactions, and permission checks, from the core business code and gathers it in one place. You write the additional behavior only once and specify separately "where to apply it", so the core code stays clean and a change to that behavior is made in one place.
Advice
In AOP, this is the code to be added to a method call, defined together with when that code runs. In Spring, you create it by putting one of these annotations on a method of an aspect class: @Before (before the method runs), @AfterReturning (after it returns a value normally), @AfterThrowing (after an exception goes out), @After (after it finishes, whether normally or with an exception), or @Around (wraps the method execution before and after). With @Around, the target method runs only when proceed() of ProceedingJoinPoint is called, and when it is not called, the target method does not run. @AfterThrowing only observes the exception passing through and does not remove it, so the exception is passed on to the caller as it is.
Pointcut
This is the condition expression that selects which methods advice is applied to. The form execution(return-type package.class.method(parameters)) is used most often, where * means any single name and (..) means any number of parameters. Empty parentheses () match only a method that takes no parameters. @annotation(annotation type) matches the methods annotated with that annotation. When an expression matches no method at all, there is no error and the advice simply does not run, so after writing an expression you must check that it is actually applied.
Aspect and @Order
An aspect is a class that gathers pointcuts and advice in one place, and in Spring you create one by putting @Aspect on a class that is registered as a bean. When several aspects apply to one method, the aspect with the lower @Order value wraps on the outside. The outer aspect runs first on the way in and last on the way out. The order among aspects that have no @Order is undefined, so always specify it when the order affects the result.
Proxy
This is a stand-in object that receives calls in place of the real object. When it receives a call, it does some additional work and then passes the call on to the real object. Spring wraps a bean that needs a feature such as AOP or @Transactional in such an object and registers it in the container, so what other beans receive by injection is this stand-in, not the real object.

Try these

Running with the default settings

  1. When the application starts: Spring checks the pointcut against each method of the beans, one by one. execution(* shop.OrderService.*(..)) matches every method of the shop.OrderService class. The * at the front means any return type, the * in the position of the method name means any name, and (..) means any number of parameters.
  2. order(String) checked against the pointcut: order is a method of OrderService, so it is matched. A proxy (a stand-in object) is registered in place of the OrderService bean (the original object and the proxy are different objects).
  3. orderService.order("keyboard"): Another bean calls the method. The call reaches the proxy first. The proxy runs the advice that applies to this method in order, starting from the outside.
  4. The first part of the @Around of LogAspect: Within one aspect, @Around starts first. The lines written above pjp.proceed() run.
  5. Call to pjp.proceed() → the @Before of LogAspect: When @Around passes the call inward, the @Before of the same aspect runs. After that comes the method body.
  6. order(String) body runs: It prints "order body" and throws an IllegalStateException. The exception goes out from the inside to the outside, back along the path by which the call came in.
  7. The @AfterThrowing of LogAspect: It runs only when an exception came out of the inside. It does not catch and remove the exception but only observes the exception passing through, so the exception keeps going outward. @AfterReturning does not run.
  8. The @After of LogAspect: It runs even though an exception was thrown. It is similar to finally in Java. Within the same aspect, it comes after @AfterThrowing.
  9. The second part of the @Around of LogAspect: pjp.proceed() threw an exception, so the "Log @Around after" line written below it does not run and the exception goes out unchanged. To run that line even on an exception, you must wrap the call in try-finally.
  10. The caller: It receives an IllegalStateException. No advice caught the exception, so the exception is passed on unchanged.

Result of this run

Code

// All the classes below are in the shop package
// @Audit is an annotation written for this example (a marker put on methods)
// Code that calls the method from another bean: orderService.order("keyboard");

@Aspect
@Component
class LogAspect {
    @Pointcut("execution(* shop.OrderService.*(..))")
    void pick() { }                          // selects which methods the advice applies to

    @Around("pick()")
    Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Log @Around before");
        Object result = pjp.proceed();       // passes the call inward
        System.out.println("Log @Around after");
        return result;
    }

    @Before("pick()")
    void before() { System.out.println("Log @Before"); }

    @AfterReturning("pick()")
    void afterReturning() { System.out.println("Log @AfterReturning"); }

    @AfterThrowing("pick()")
    void afterThrowing() { System.out.println("Log @AfterThrowing"); }

    @After("pick()")
    void after() { System.out.println("Log @After"); }
}

@Service
class OrderService {
    public String order(String item) {
        System.out.println("order body");
        throw new IllegalStateException("failed");
    }

    @Audit
    public String cancel(Long id) {
        System.out.println("cancel body");
        return "cancellation completed";
    }
}

@Service
class MemberService {
    @Audit
    public String join(String name) {
        System.out.println("join body");
        return "sign-up completed";
    }
}

Assumptions of this model

All 72 combinations were run on Spring Framework 7.0.9 (the version included in Spring Boot 4.1.1, with AspectJ weaver 1.9.25.1 and JDK 21) on this PC, using code with the same structure as on the screen, and these were checked against the results: whether the pointcut matches the method, the order of the lines printed on the console, what the caller receives, and whether the bean is a proxy. It was also confirmed by execution that all 72 results are the same when the advice methods are written in the class in the reverse order, and that the method body does not run when @Around does not call pjp.proceed(). Two things could not be confirmed by execution and follow the Spring documentation. First, the order of execution within one aspect has been defined as it is now since Spring Framework 5.2.7, and in older versions @After sometimes ran before @AfterReturning (this PC has no old version). Second, the order among aspects that have no @Order is undefined. The code on the screen assumes that @EnableAspectJAutoProxy is turned on (for the runs, it was put directly on the configuration class; the documentation says that in Spring Boot it is turned on automatically when the AOP starter is added, but this part was not confirmed by execution). The method is assumed to be called from another bean. When it is called from within the same class, the call does not go through the proxy and the advice does not run, which is covered in the "Proxies and self-invocation" lab. The pointcut method is named pick. On this PC, when it was named target, none of the advice of LogAspect ran, and there was no error (target is a word that has its own use in pointcut expressions).

Previous lab: Proxies and self-invocation · Next lab: Transaction basics

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these