Implementation Principle of Spring AOP

Keywords: Spring JDK Programming Java

AOP (Aspect Orient Programming), commonly known as Aspect Oriented Programming, is a complement to Object Oriented Programming, which is used to process cross-cutting concerns distributed in various modules, such as transaction management, logging, caching and so on. The key of AOP implementation lies in the AOP proxy created automatically by AOP framework. AOP proxy is mainly divided into static proxy and dynamic proxy. AspectJ is the representative of static proxy, while Spring AOP is the representative of dynamic proxy. This paper will analyze and introduce the implementation of AspectJ and Spring AOP respectively.

Implementing AOP with AspectJ compile-time enhancement

As mentioned earlier, AspectJ is an enhancement of static proxy, so-called static proxy is that AOP framework generates AOP proxy classes at compile stage, so it is also called compile-time enhancement.

Take an example. First we have a generic Hello class

public class Hello {
    public void sayHello() {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        Hello h = new Hello();
        h.sayHello();
    }
}

Writing an Aspect with AspectJ

public aspect TxAspect {
    void around():call(void Hello.sayHello()){
        System.out.println("Start a business ...");
        proceed();
        System.out.println("End of transaction ...");
    }
}

A transaction scenario is simulated here, similar to Spring's declarative transaction. Compiler compilation using AspectJ

ajc -d . Hello.java TxAspect.aj

After compilation, run the Hello class, and you can see the following output

Start a business...
hello
 The transaction ends...

Obviously, AOP is in effect, so how does AspectJ add new functionality to Hello classes without modifying them?

Check out the compiled Hello.class

public class Hello {
    public Hello() {
    }

    public void sayHello() {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        Hello h = new Hello();
        sayHello_aroundBody1$advice(h, TxAspect.aspectOf(), (AroundClosure)null);
    }
}

As you can see, this analogy has some more code than the original Hello.java. This is AspectJ's static proxy, which weaves Aspect into Java bytecode at compilation stage and runs as an enhanced AOP object.

public void ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983(AroundClosure ajc$aroundClosure) {
        System.out.println("Start a business ...");
        ajc$around$com_listenzhangbin_aop_TxAspect$1$f54fe983proceed(ajc$aroundClosure);
        System.out.println("End of transaction ...");
    }

The logic of execution can be seen more clearly from the class file compiled by Aspect. The proceed method is the method in the proxy class that calls back the execution.

Using Spring AOP

Unlike AspectJ's static proxy, Spring AOP uses dynamic proxy. The so-called dynamic proxy means that the AOP framework does not modify bytecode, but temporarily generates an AOP object for the method in memory. The AOP object contains all the methods of the target object, and enhances them at specific cut points, and calls back the methods of the original object.

There are two main ways of dynamic proxy in Spring AOP: JDK dynamic proxy and CGLIB dynamic proxy. The JDK dynamic proxy receives the proxy class by reflection, and requires the proxy class to implement an interface. The core of JDK dynamic proxy is InvocationHandler interface and Proxy class.

If the target class does not implement the interface, Spring AOP will choose to use CGLIB to dynamically proxy the target class. CGLIB (Code Generation Library) is a code-generated class library, which can dynamically generate subclasses of a class at runtime. Note that CGLIB is a dynamic proxy by inheritance, so if a class is marked final, it can not use CGLIB as a dynamic proxy.

To verify the above statement, a simple test can be done. First, test the implementation of the interface.

Define an interface

public interface Person {
    String sayHello(String name);
}

Implementation class

@Component
public class Chinese implements Person {

    @Timer
    @Override
    public String sayHello(String name) {
        System.out.println("-- sayHello() --");
        return name + " hello, AOP";
    }

    public void eat(String food) {
        System.out.println("I'm eating:" + food);
    }

}

The @Timer annotation here is a common annotation I defined to mark Pointcut.

Define Aspect

@Aspect
@Component
public class AdviceTest {

    @Pointcut("@annotation(com.listenzhangbin.aop.Timer)")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void before() {
        System.out.println("before");
    }
}

Function

@SpringBootApplication
@RestController
public class SpringBootDemoApplication {

    //Here you must use the Person interface for injection
    @Autowired
    private Person chinese;

    @RequestMapping("/test")
    public void test() {
        chinese.sayHello("listen");
        System.out.println(chinese.getClass());
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}

output

before
-- sayHello() --
class com.sun.proxy.$Proxy53

You can see that the type is com.sun.proxy.$Proxy53, which is the proxy class mentioned earlier, so Spring AOP uses JDK's dynamic proxy here.

Let's look again at the situation where the interface is not implemented and modify the Chinese class

@Component
public class Chinese {

    @Timer
//    @Override
    public String sayHello(String name) {
        System.out.println("-- sayHello() --");
        return name + " hello, AOP";
    }

    public void eat(String food) {
        System.out.println("I'm eating:" + food);
    }

}

Function

@SpringBootApplication
@RestController
public class SpringBootDemoApplication {

    //Direct Injection with Chinese Class
    @Autowired
    private Chinese chinese;

    @RequestMapping("/test")
    public void test() {
        chinese.sayHello("listen");
        System.out.println(chinese.getClass());
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}

output

before
-- sayHello() --
class com.listenzhangbin.aop.Chinese$$EnhancerBySpringCGLIB$$56b89168

You can see that the class is enhanced by CGLIB, which is called dynamic proxy. The CGLIB proxy here is Spring AOP proxy, which is also called AOP proxy. AOP proxy class dynamically weaves enhancement processing at the tangent point.

Summary

AspectJ enhances the target object at compile time, Spring AOP's dynamic proxy enhances dynamically at each run time, and generates AOP proxy object. The difference is that the timing of generating AOP proxy object is different. Compared with AspectJ's static proxy method, AspectJ has better performance, but AspectJ needs a specific compiler to process, while Spring AOP does not need a specific compiler. Li.

Posted by tmann on Wed, 26 Jun 2019 14:18:35 -0700