Dev Learning Lab

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

Fundamentals › Spring

AOP and proxies

What is a proxy, and how does Spring step in before and after my method?

When you put an annotation such as @Transactional on a method, something we did not write happens before and after that method when it is called. This is because Spring does not use the object it made from the class we wrote as it is. It creates a stand-in object (a proxy) that receives the calls in its place, and keeps that. This lab shows the same thing with a LogAspect that we write ourselves, instead of @Transactional. LogAspect is a class with @Aspect, and it is the place where we write separately what to insert into many methods in common (here, one line of log). The execution(* OrderService.order(..)) inside @Before is an expression that selects the order() method of the OrderService class. Writing separately what to insert in common like this is called AOP. If @Autowired is on the orderService field of the calling class Caller, the object that Spring keeps is put in that field. In this lab you remove LogAspect, or put an object made directly with new OrderService() in that field, and see what the same order() call prints and what the object that received the call is.

Checked against real execution · Ran all four combinations with real @Aspect proxies on Spring 7.0.9 and AspectJ weaver 1.9.25.1, and checked the printed lines and the class of the called object

Concepts in this lab

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.
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.
@Aspect and @Before
@Aspect is an annotation that you put above a class to mark that the class is an aspect (a class where behavior to insert into many methods in common is written). An aspect also has to be a bean managed by Spring, so to have it found by class path scanning, @Aspect alone is not enough, and you must also add an annotation such as @Component. @Before is put above a method of the aspect, and a pointcut expression is written in its parentheses. This method runs before the method that the expression selects runs. When Spring decides that an aspect applies to a bean, it creates a proxy of that bean automatically (auto-proxying). To use this feature, you must turn on @AspectJ support, and the aspectjweaver library must be on the class path.
@Autowired
@Autowired is an annotation that you put on a field or a constructor so that Spring finds a bean of the matching type in the container and puts it in that place. When it is put on a field, Spring fills in the object even if our code has no line that puts a value in the field. If there is no matching bean, the injection fails by default. Spring looks in the beans kept by the container, so an object made directly with new in the code is a different object from the bean the container kept.

Try these

Running with the default settings

  1. When the program starts, Spring finds the classes with @Component or @Service (component scanning), creates an object of each, and keeps it. An object that Spring creates and keeps like this is called a bean. It also creates one object of the OrderService class. The code we wrote has no line new OrderService(). It also creates an object of the LogAspect class, which has @Component.
  2. Spring finds LogAspect, which has @Aspect. The execution(* OrderService.order(..)) inside @Before is an expression that selects the order() method of the OrderService class (with any number of parameters). The OrderService object that was just created has an order() that matches this expression. So Spring does not use this object as the bean as it is. It creates a stand-in object (a proxy) that receives the calls in place of this object, and keeps that as the bean. The proxy is an object of the class OrderService$SpringCGLIB$0, which extends OrderService, and it holds a reference to the real OrderService object. They are two different objects. The code we wrote has no line that creates the proxy.
  3. Spring also creates an object of the Caller class. The orderService field has @Autowired, so Spring looks for a bean of the type OrderService to put in this field, and puts it in. We did not write a line that puts a value in the field. What went into this field now is the proxy that Spring kept.
  4. run() of Caller calls orderService.order(). What is in the orderService field is the proxy, so the call arrives first at the proxy, not at the real OrderService. The proxy knows that order() is a method that matches the @Before expression of LogAspect, so before it calls the real order(), it first runs log() of LogAspect.
  5. The proxy runs log() of LogAspect and prints "[log] order called". This line is written in LogAspect, not in order().
  6. The proxy calls order() of the real OrderService object that it holds a reference to, and order() prints "Order processed". There is no line that prints a log inside order().
  7. Next, run() prints the class name of the object in the orderService field with the getSimpleName() method. The printed name is "OrderService$SpringCGLIB$0". It is not OrderService but another class that Spring made while the program was running. This name does not appear in the code we wrote. It is a sign that we are calling a proxy.

Result of this run

Code

// Inserted behavior: runs every time order() of the OrderService bean kept by Spring is called
@Aspect
@Component
class LogAspect {
    @Before("execution(* OrderService.order(..))")
    void log() {
        System.out.println("[log] order called");
    }
}

@Service
class OrderService {
    public void order() {
        System.out.println("Order processed");
    }
}

@Component
class Caller {
    // Spring finds the bean it made and puts it here
    @Autowired
    OrderService orderService;

    void run() {
        orderService.order();
        System.out.println(orderService.getClass().getSimpleName());
    }
}

Assumptions of this model

Checked against Spring Framework 7.0.9 (AspectJ weaver 1.9.25.1) running on JDK 21 with AnnotationConfigApplicationContext. All four combinations were checked by compiling the same source as the code on the screen (with only the package and import lines added) and starting a new child Java process for each combination. The configuration class had @EnableAspectJAutoProxy and @ComponentScan, and the classes were found by scanning (this configuration is not shown on the screen). What was checked is the lines that run() printed to the screen and their order, the class name of the called object and whether it is a proxy as measured with AopUtils, the class of the OrderService bean taken from the Spring container and whether it is a proxy, whether the called object and that bean are the same object, and the number of proxies in the container. The facts that the step sentences rely on were also checked by execution: that the proxy is a class that extends OrderService and has one real OrderService object inside it, that the object Spring put in is the same as the bean kept in the container, and that an object made with new is different from that bean. The proxy class name OrderService$SpringCGLIB$0 is the name that was actually printed on the PC used to write this lab. What the documentation defines comes from the Core chapter of the Spring Framework 7.0 documentation: the AOP Proxies section (that Spring AOP is proxy-based, and that an object without an interface is proxied with CGLIB by default), the Understanding AOP Proxies section (that the advice runs only when the call reaches the proxy), the Enabling @AspectJ Support section (that aspectjweaver must be on the class path, and that a proxy is created automatically for a bean that has advice), and the Declaring an Aspect section (that @Aspect alone is not picked up by scanning and @Component is needed). However, the documentation does not state in one sentence the order in which "the proxy runs @Before before it calls the real object inside", so that order is an observation confirmed by the order of the lines the probe program printed. OrderService in this lab is a class without an interface, so a CGLIB proxy was created. This lab does not cover a class that implements an interface, where a JDK dynamic proxy may be used, a call made inside the same class (self-invocation), or many methods and many aspects, and the diagram is simplified to show only the code on the screen. The case where AOP is turned on by the auto-configuration of Spring Boot was not run either. The code on the screen leaves out the package and import lines. The exact time when @Before runs and the rules of pointcut expressions are covered in the in-depth lab "AOP: advice and pointcuts", and self-invocation and the proxy types (JDK dynamic proxy, CGLIB) are covered in the in-depth lab "Proxies and self-invocation".

Previous lab: Dependency injection · Next lab: Servlets

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these