Spring AOP core source code analysis takes you into the world of AOP

Posted by oscar2 on Fri, 04 Feb 2022 09:22:27 +0100


Title 1-5 of this article will use the knowledge of title 6: an introduction to how to view the source code of dynamically generated proxy classes. If you are interested, you can take a look at the short video!

1. Spring AOP overview

AOP, aspect oriented programming, is a programming idea and abstraction. AspectJ and Spring AOP implement AOP respectively.

Aspect is a more general AOP framework. Like other languages, it also has corresponding implementations, such as AspectC, AspectC + +, aspect PHP, aspectnet, etc.

AspectJ is introduced into Spring AOP only to define aspects (AOP support based on @ AspectJ), but the runtime is still pure Spring AOP and does not depend on AspectJ Weaver (ajc).

The difference between Advice and Advisor: Advice is notification (such as before notification, after notification, etc.), advisor is notifier (enhancer), and the source code of advisor interface is as follows. It can be seen that advisor and Advice are one-to-one relationship.

package org.springframework.aop;
import org.aopalliance.aop.Advice;

public interface Advisor {
	Advice EMPTY_ADVICE = new Advice() {};
	Advice getAdvice();
	boolean isPerInstance();
}

Tips:

  • The concept of join point matching is the key of AOP.
  • Spring Aop only supports method interception, not field interception
  • The purpose of Spring AOP is not to provide the most complete AOP implementation (although Spring AOP is very capable), but to provide close integration between AOP implementation and Spring IoC to help solve common problems in enterprise applications
  • jdk dynamic proxy only supports public methods. Although cglib supports public, protected and package methods, the proxy method should be public

2. Spring AOP terminology

Aspect: there are two ways to define aspects: xml based and @ AspectJ based

Join point: a point in the process of program execution, such as method execution or exception handling. In Spring AOP, a join point always represents a method execution

Advice: the action taken by the aspect at a specific connection point

PointCut: predicate that matches the join point. Advice is associated with a PointCut expression and runs at any join point that matches the PointCut (for example, executing a method with a specific name). Spring uses AspectJ PointCut expression language by default

Target object

Proxy object

Weaving: it can be completed at compile time (special compiler is required, such as AspectJ's ajc compiler), class load time (special class loader is required, such as AspectJ5's load time weaving, LTW) and runtime (dynamic agent)

Notification type

  • Before: before the method is executed (no exception is allowed)

  • After returning: the method runs before returning (no exceptions are allowed)

  • After throwing: runs if the method exits because of an exception thrown

  • After: after the method runs normally or throws an exception, it will run

  • Around: This is the most general suggestion, which is more powerful. Spring officials suggest that you should try not to use around notifications. For example, you should cache the method return value. You should give priority to After returning rather than around

3. One kind of intensifier: introduction Advisor

IntroductionAdvisor and PointcutAdvisor are all subclasses of the Advisor interface and enhancers. Let's see how Introduction is used through the code.

@Component("mailBean")
public class MailBean {
    public void sayHello(){
        System.out.println("--MailBean hello--");
    }
}
public interface IMailService {
    void send();
}
public class DefaultMailServiceImpl implements IMailService {
    @Override
    public void send() {
        System.out.println("-- MailServiceImp::send--");
    }
}
@Aspect
@Component
public class IntroductionAspect {
    // The key point here is that a bean will be generated through dynamic proxy. The bean inherits from MailBean and implements IMailService interface, which is equivalent to adding a send method to MailBean
    // When the send method is executed, it will be intercepted by Introduction Advice and the send method of DefaultMailServiceImpl will be executed
    @DeclareParents(value = "com.bobo.springbootdemo.introduction.MailBean", defaultImpl = DefaultMailServiceImpl.class)
    private IMailService iMailService;
}
@SpringBootTest
@RunWith(SpringRunner.class)
public class IntroductionTests {
    @Autowired
    private ApplicationContext context;
    @Test
    public void test(){
        IMailService mailService = context.getBean("mailBean", IMailService.class);
        MailBean mailBean = context.getBean("mailBean", MailBean.class);
        mailService.send(); // Output -- MailServiceImp::send--
        mailBean.sayHello();// Output -- MailBean hello--
    }
}
// The dynamically generated proxy class is declared as follows
public class MailBean$$EnhancerBySpringCGLIB$$73ed774c extends MailBean implements IMailService, SpringProxy, Advised, Factory

Finally, let's talk about the official introduction of spring: introduction represents the definition of a new field or method added to a class. Spring AOP allows us to introduce a new interface implementation for any object at a glance.

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
	// Neither MethodMatcher nor PointCut is required, as long as the ClassFilter filters out the matching classes
	ClassFilter getClassFilter();
	void validateInterfaces() throws IllegalArgumentException;
}

How to associate the IntroductionAdvisor with the IntroductionInterceptor? Here, take DefaultIntroductionAdvisor and DelegatingIntroductionInterceptor as examples to realize the same functions of DeclareParents annotation by programming.

public interface IMailService {
    void send();
}
public class MailBean {
    public void sayHello(){
        System.out.println("--MailBean hello--");
    }
}
public class IntroductionApiInterceptor extends DelegatingIntroductionInterceptor implements IMailService{
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        // return mi.proceed(); //  It cannot be used in this way because the original MailBean class has no send method
        return super.invoke(mi); // Execute the send method that the original MailBean class does not have, which is Introduction
    }
    @Override
    public void send() {
        System.out.println("-- IntroductionApiInterceptor::send--");
    }
}
public class IntroductionApiAdvisor extends DefaultIntroductionAdvisor {
    public IntroductionApiAdvisor(){
        super(new IntroductionApiInterceptor(), IMailService.class);
    }
}
@Test
public void test(){
	ProxyFactory factory = new ProxyFactory(new MailBean());
    factory.setProxyTargetClass(true);
    factory.addAdvisors(new IntroductionApiAdvisor());
    MailBean mailBean = (MailBean)factory.getProxy();
    IMailService mailService = (IMailService)proxy;
    mailBean.sayHello(); // Output -- MailBean hello--
    mailService.send(); // Output -- MailServiceImp::send--
}
// The dynamically generated proxy class is declared as follows
public class MailBean$$EnhancerBySpringCGLIB$$550bae40 extends MailBean implements IMailService, SpringProxy, Advised, Factory 

4,Spring AOP API

1. Create proxies programmatically based on AspectJ

AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// The SecurityManager class must be annotated with @ Aspect
factory.addAspect(SecurityManager.class);
// You can also use an instance, but it also needs to be annotated with @ Aspect
factory.addAspect(usageTracker);
// Generate proxy object
MyInterfaceType proxy = factory.getProxy();

2. Top level interface

// Intensifier
public interface Advisor {
	Advice EMPTY_ADVICE = new Advice() {};
	Advice getAdvice();
	boolean isPerInstance();
}
// notice
public interface Advice {
}
// Entry point enhancer
public interface PointcutAdvisor extends Advisor {
	Pointcut getPointcut();
}
// breakthrough point
public interface Pointcut {
	// The ClassFilter interface is used to limit pointcuts to a given set of target classes
	ClassFilter getClassFilter();
    // Usually more important than ClassFilter
	MethodMatcher getMethodMatcher();
	Pointcut TRUE = TruePointcut.INSTANCE;
}
public interface MethodMatcher {
    // Static pointcuts only need to call the matches method with 2 parameters
    boolean matches(Method m, Class<?> targetClass);
    boolean isRuntime();
    // For dynamic pointcuts, when the 2-parameter matches method and isRuntime return true, the 3-parameter matches method must be called every time the method is executed (because the method parameters need to be considered)
    boolean matches(Method m, Class<?> targetClass, Object... args);
}

Static pointcut (only considering classes and methods): subclasses only need to implement StaticMethodMatcherPointcut, such as regular expression pointcut: JdkRegexpMethodPointcut.

Dynamic pointcut (also consider method parameters): subclasses need to implement DynamicMethodMatcherPointcut

3. Interception and notification interface

public interface Interceptor extends Advice {
}
// Around notification
public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}
public interface BeforeAdvice extends Advice {
}
// Before notice
public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method m, Object[] args, Object target) throws Throwable;
}
public interface AfterAdvice extends Advice {
}
// throw notification. throw notification does not need to implement any method, so you need to add your own method: void afterThrowing(Method m, Object[] args, Object target, ServletException ex)
public interface ThrowsAdvice extends AfterAdvice {
}
// After Return notification
public interface AfterReturningAdvice extends AfterAdvice {
	void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
}
// Introduction Advice, which is a special notice. See Section 2.3 for the usage of IntroductionInterceptor
public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {
}

4. Create proxies programmatically

ProxyFactory factory = new ProxyFactory(new MailBean());
factory.setProxyTargetClass(true);
factory.addAdvisors(new IntroductionApiAdvisor());
MailBean mailBean = (MailBean)factory.getProxy();
// After creating the agent, you can still add or delete enhancers and notifications
Advised advised = (Advised) mailBean;
Advisor[] advisors = advised.getAdvisors();
advised.addAdvice(new DebugInterceptor());
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

Properties provided by ProxyConfig:

  • proxyTargetClass: whether to proxy the target class

  • Optimize: whether to optimize. If you don't understand the optimization principle, don't use it easily

  • frozen: if true, configuration changes are not allowed. Set to true if you do not want the caller to be able to manipulate the proxy (through the Advised interface), and the default is false

  • exposeProxy: determines whether the current proxy should be exposed in ThreadLocal so that the target can access it (through AopContext.currentProxy())

Properties provided by ProxyFactoryBean:

  • proxyInterfaces:String interface name array. If not provided, the CGLIB proxy of the target class is used
  • interceptorNames: String array of Advisor, interceptor or or other Advice. These names are the bean names in the current factory, and the * sign can be used
  • Singleton: whether it is a singleton. The default value is true

Example of ProxyFactoryBean:

// Define target class
@Component
public class MessageBean {
    public void sendMsg(){
        System.out.println("-- MessageBean sendMsg --");
    }
}
// Define pre notification
@Component
public class MessageBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("-- MessageBeforeAdvice before --");
    }
}
// Define enhancer and inject pre notification
@Component("messageAdvisor")
public class MessageAdvisor extends StaticMethodMatcherPointcutAdvisor {
    @Autowired
    private MessageBeforeAdvice messageBeforeAdvice;

    @PostConstruct
    public void init(){
        super.setAdvice(messageBeforeAdvice);
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        if(targetClass.getName().contains("MessageBean") && method.getName().contains("send")){
            return true;
        }
        return false;
    }
}
// Define post notification
@Component("messageAfterAdvice")
public class MessageAfterAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("-- MessageAfterAdvice afterReturning --");
    }
}
// Configure ProxyFactoryBean
@Configuration
public class ProxyFactoryBeanConfig {
    @Autowired
    private MessageBean messageBean;
    @Bean
    public ProxyFactoryBean proxyFactoryBean(){
        ProxyFactoryBean bean = new ProxyFactoryBean();
        bean.setTarget(messageBean);
        bean.setProxyTargetClass(true);
        bean.setInterceptorNames("messageAdvisor","messageAfterAdvice");
        bean.setSingleton(true);
        return bean;
    }
}
// unit testing 
@Test
public void test3(){
    ProxyFactoryBean proxyFactoryBean = context.getBean(ProxyFactoryBean.class);
    MessageBean proxy = (MessageBean)proxyFactoryBean.getObject();
    proxy.sendMsg();
}
Final output
// -- MessageBeforeAdvice before --
// -- MessageBeforeAdvice before --
// -- MessageBean sendMsg --
// -- MessageAfterAdvice afterReturning --

5. Auto proxy

Previously, proxy is created for the target class through ProxyFactory or ProxyFactoryBean, but although this method is flexible, it is not conducive to management. Next, consider using the automatic proxy function.

Automatic proxy is based on BeanPostProcessor, org springframework. aop. framework. The AutoProxy package provides some ready-made automatic proxy classes

  • BeanNameAutoProxyCreator: match by beanName wildcard. If the match is successful, the proxy will be created
  • Defaultadvisor autoproxycreator: more versatile and powerful

5. Spring AOP underlying principle - Dynamic Proxy

1. JDK dynamic agent

public interface IProductService {
    void update();
}
public class ProductServiceImpl implements IProductService {
    @Override
    public void update() {
        System.out.println("-- ProductServiceImpl update --");
    }
}
public class ProductHandler implements InvocationHandler {
    private Object target;
    public ProductHandler(Object target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before advice");
        // You must pass in the target object here
        Object res = method.invoke(target,args);
        System.out.println("after advice");
        return res;
    }
}
public class JdkDynamicProxyTests {
    public static void main(String[] args) {
        Object obj = Proxy.newProxyInstance(JdkDynamicProxyTests.class.getClassLoader(),
                new Class[]{IProductService.class},
                new ProductHandler(new ProductServiceImpl()));
        IProductService productService = (IProductService)obj;
        productService.update();
    }
}
// Finally, the generated proxy class signature is as follows
// public final class $Proxy0 extends Proxy implements IProductService 

2. Cglib dynamic agent

public class ProductServiceImpl implements IProductService {
    @Override
    public void update() {
        System.out.println("-- ProductServiceImpl update --");
    }
}
public class ProductCallback implements MethodInterceptor {
    // obj: generated proxy object; Method: target method; methodProxy: target method + proxy method
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before advice");
        Object res = methodProxy.invokeSuper(obj,args);
        System.out.println("after advice");
        return res;
    }
}
public class CglibDynamicProxyTests {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ProductServiceImpl.class);
        enhancer.setCallback(new ProductCallback());
        ProductServiceImpl service = (ProductServiceImpl)enhancer.create();
        service.update();
    }
}

Note: distinguish between org. Org aopalliance. intercept. Methodinterceptor and org springframework. Cglib. proxy. Methodinterceptor, the former is a subclass of Advice and notification, and the latter is a subclass of Callback. It is used for Cglib to dynamically weave into the proxy class when generating the proxy class.

6. How to view the dynamically generated proxy class source code?

How to view the source code of dynamic proxy class

Video link: https://www.bilibili.com/video/BV1fb4y1j7LP/

Topics: Java Spring Back-end AOP