Analysis of Aop bottom layer of Spring

Posted by kr9091 on Thu, 03 Mar 2022 05:28:35 +0100

Analysis of the underlying source code of Aop in Spring (Part I)

As the author becomes more and more lazy [I haven't updated my blog for a long time], I can't afford time to record or analyze some technical points alone, because the blog is still short to make readers willing to read it in their heart.

1, Aop application

Dynamic agent

Explanation of proxy mode: provide a proxy for other objects to control access to this object, enhance a method in a class, and extend the program.

For example, there is now an ItemService class:

@Component
public class ItemService{
public  void  testAop(){
    
     System.out.println("testAop");

	}
}

At this point, we create a new ItemService object, and then execute the testAop () method. The result is obvious.

If we want to add additional logic to testAop() without modifying the source code of ItemService class, we can use dynamic proxy mechanism to create ItemService object, such as:

1.1 using Cglib agent

		Enhancer enhancer = new Enhancer();

		ItemService target = new ItemService();

		enhancer.setSuperclass(ItemService.class);
        // Define agent logic
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
              // o is a proxy object
				System.out.println("cglib");
			    // There are four ways to write, and the third one will cause stack overflow
				//Object o1 = method.invoke(target, objects);
				// Object o1 = method.invoke(o, objects);
				// Object o1 = methodProxy.invoke(target, objects);
				Object o1 = methodProxy.invokeSuper(o, objects);
				return o1;
			}
		});
        // itemService object created by dynamic agent
		Object o = enhancer.create();
		return o;
        // When the test method of this itemService is executed, some additional logic will be executed
		ItemService itemService = (ItemService) CglibUtil.getProxy();
		itemService.test();

The results are as follows:

All the objects obtained are itemService objects, but the effect of executing the testAop() method is different. This is the effect brought by the agent.

The above is the creation of proxy object through cglib, which is based on parent-child classes. The proxied class (ItemService) is the parent class, the proxy class is the child class, the proxy object is the instance object of the proxy class, and the proxy class is created by cglib, which is of no concern to programmers.

Using the above cglib proxy, you can find a problem. If there are multiple methods in the target class, the generated proxy object will proxy all the methods of the proxy object. For example, what should we do if we only want to delegate some methods in the target object differently? We can also continue to transform, such as:

         Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(ItemService.class);
		enhancer.setUseFactory(false);
         // Pass an array to store our multiple proxy logic
	    enhancer.setCallbacks(new Callback[]{new MethodInterceptor() {
			@Override
			public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
				System.out.println("cglib----before");
				//Object o1 = methodProxy.invokeSuper(o, objects);
				Object o1 = methodProxy.invokeSuper(o, objects);
				System.out.println("cglib----after");
                return o1;
			}
		},NoOp.INSTANCE});
        // Set up an interceptor to act as a targeted agent for the needs of programmers
	    enhancer.setCallbackFilter(new CallbackFilter() {
			@Override
			public int accept(Method method) {
                // The proxy for the target method is test
				if(method.getName().equals("test")){
					return 0;
				}
                // The target method is another proxy
				return 1;
			}
		});
		Object o = enhancer.create();
		return o;

1.2 using Jdk agent

Apart from cglib technology, jdk itself also provides a dynamic proxy mechanism for creating proxy objects, but it can only proxy interfaces, that is, itemService must have an interface before using jdk dynamic proxy mechanism to generate a proxy object, such as:

public interface ItemServices {

	public  void  testAop();

	public  void  test();


	public  int  index();
}

public class ItemService implements  ItemServices{

	@Override
	public  void  testAop(){
		System.out.println("testAop");
	}
	@Override
	public  void  test(){

		System.out.println("test");

	}
	@Override
	public int index() {
		return 0;
	}

}

Use JDK dynamic proxy to generate a proxy object:

        
         ItemService target =new ItemService();

         // The proxy object of ItemServices interface. Note that this must be an interface type, because you proxy the interface, and you cannot use an implementation class that implements this interface. Because there may be many types of implementation classes to implement this interface in the future. Not necessarily of type ItemServices.
		ItemServices itemServices = (ItemServices) Proxy.newProxyInstance(JdkUtil.class.getClassLoader(), new Class[]{ItemServices.class}, new InvocationHandler() {
			@Override
			public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
				System.out.println("jdk  before");
				Object invoke = method.invoke(target, objects);
				System.out.println("jdk  after");
				return invoke;
			}
		});


		return itemServices;

		ItemServices proxy = JdkUtil.getProxy();
		proxy.test();

If you replace new Class[]{ItemServices.class} with new Class[]{ItemService.class}, the allowable code will directly report an error:

Exception in thread "main" java.lang.IllegalArgumentException: com.hzk.test.CircleService.Aop.service.ItemService is not an interface

Indicates that it must be an interface.

Due to this limitation, the type of proxy object generated is ItemServices, not ItemService, which should be noted.

1.3 ProxyFactory

The author introduced two kinds of dynamic proxy technologies above, and encapsulated them in Spring. The encapsulated class is called ProxyFactory, which means that it is a factory for creating proxy objects. It will be more convenient to use than the above, for example:

	    ItemService target = new ItemService();

		ProxyFactory proxyFactory = new ProxyFactory();
        
        // Proxied object
		proxyFactory.setTarget(target);
        //Set the interface proxy mode and adopt jdk dynamic proxy 
        //proxyFactory.setInterfaces(ItemServices.class);
        // Agent logic
        proxyFactory.addAdvice(new MethodBeforeAdvice() {
			@Override
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println("before-----");
			}
		});
         //Generate proxy object
         ItemService itemService = (ItemService) proxyFactory.getProxy();
		itemService.test();

The test results are as follows:

Through ProxyFactory, we can no longer decide whether to use cglib or jdk dynamic proxy. ProxyFactory will help us judge. If ItemService implements the interface, the bottom layer of ProxyFactory will use jdk dynamic proxy. If it does not implement the interface, it will use cglib technology. The above code is because ItemService does not implement the ItemServices interface, Therefore, the last generated proxy object is of type ItemService.

Open / / proxyfactory setInterfaces(ItemServices.class); notes

Classification of Advice

  1. Before Advice: execute before method
  2. After returning advice: the method is executed after returning
  3. After throwing advice: the method executes after throwing an exception
  4. After (finally) advice: the method is executed after it is finally executed. This is the last, which is later than return
  5. Around advice: This is the most powerful Advice. You can customize the execution order

Advisor understanding

Similar to Advice, there is also a concept of Advisor. An Advisor is composed of a Pointcut and an Advice. The logic to be proxied can be specified through the Pointcut. For example, there are two methods in an ItemService class. According to the above example, both methods will be proxied and enhanced. Now we can use Advisor, To control which method the specific agent uses, for example:

 proxyFactory.addAdvisor(new PointcutAdvisor() {
		  @Override
		  public Pointcut getPointcut() {
			  return new StaticMethodMatcherPointcut() {
				  @Override
				  public boolean matches(Method method, Class<?> targetClass) {
					if(method.getName().equals("test")){
						return true;
					}
				  	return false;
				  }
			  };
		  }

	  @Override
		  public Advice getAdvice() {
			  return new MethodInterceptor() {
				  @Override
				  public Object invoke(MethodInvocation invocation) throws Throwable {
					  System.out.println("before...");
					  Object proceed = invocation.proceed();
					  System.out.println("after...");
					  return proceed;
				  }
			  };
		  }

		  @Override
		  public boolean isPerInstance() {
			  return false;
		  }
	  });

The above code indicates that the generated proxy object will be enhanced only when the test method is executed, and will execute additional logic, but will not be enhanced when other methods are executed.

How proxy objects are created

As described above, ProxyFactory, Advisor, Advice, PointCut and other technologies are provided in spring to realize the creation of proxy objects. However, when we use spring, we will not directly use ProxyFactory. For example, we hope that the proxy object generated by ProxyFactory can be a Bean directly, and the proxy object of UserSerivce can be obtained directly from the spring container, Spring supports all these, but as developers, we must tell spring which classes need to be proxied and what the proxy logic is.

2.1 ProxyFactoryBean

	@Bean
	public  ProxyFactoryBean itemService(){

		ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
		proxyFactoryBean.setTarget(new ItemService());
	    proxyFactoryBean.addAdvice(new HzkBeforeAdvice());
		return proxyFactoryBean;
	}
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		System.out.println(context.getBean("itemService"));

This method is used to define an ItemService Bean, which has passed AOP. However, this method can only be used for one Bean. It is a FactoryBean, so it uses FactoryBean technology to indirectly take the proxy object of ItemService as a Bean.

ProxyFactoryBean also has additional functions. For example, you can define an advice or Advisor as a Bean and set it in ProxyFactoryBean

@Bean
public MethodInterceptor hzkAroundAdvise(){
 return new MethodInterceptor() {
  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
			System.out.println("before...");
   Object result = invocation.proceed();
			System.out.println("after...");
   return result;
		}
	};
}

@Bean
public ProxyFactoryBean itemService(){
     ItemService itemService = new ItemService();
     ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
	 proxyFactoryBean.setTarget(itemService);
	 proxyFactoryBean.setInterceptorNames("hzkAroundAdvise");
     return proxyFactoryBean;
}

2.2 BeanNameAutoProxyCreator

ProxyFactoryBean has to specify the proxy object by itself, so we can proxy a bean by specifying the name of the bean through BeanNameAutoProxyCreator

	@Bean
	public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
		BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
		beanNameAutoProxyCreator.setBeanNames("itemSer*");
		beanNameAutoProxyCreator.setInterceptorNames("hzkBeforeAdvice");
		return beanNameAutoProxyCreator;
	}

Through BeanNameAutoProxyCreator, you can AOP a batch of beans, and specify the proxy logic, and specify an InterceptorName, that is, an advice. The premise is that the advice must also be a Bean, so that Spring can find it. However, the disadvantage of BeanNameAutoProxyCreator is obvious. It can only specify the beans to be proxy according to beanName.

2.3 DefaultAdvisorAutoProxyCreator

	@Bean
	public DefaultPointcutAdvisor defaultPointcutAdvisor(){
		NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
		pointcut.addMethodName("test");

		DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
		defaultPointcutAdvisor.setPointcut(pointcut);
		defaultPointcutAdvisor.setAdvice(new HzkBeforeAdvice());

		return defaultPointcutAdvisor;
	}

	// After the initialization of a bean during the creation process, the defaultadvisor autoproxycreator postprocessor will find all the beans of the Advisor, and then compare them with the bean I currently create
	//Match to see if there is a test method, and then execute the proxy logic
	@Bean
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {

		DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

		return defaultAdvisorAutoProxyCreator;
	}

Through the defaultadvisor autoproxycreator, you will directly find all beans of Advisor type, and determine the beans to be proxied and the proxy logic according to the PointCut and Advice information in the Advisor.

Since this is implemented by the postprocessor, many bean s will execute the logic of the postprocessor when they go through their own life cycle. Complete agent

However, we found that in this way, we have to rely on a certain class to implement and define our Advisor, or Advise, or Pointcut. Can this step be more simplified?

Yes, through annotation!

For example, can we define only one class, and then define PointCut and Advice through some annotations on the methods in the class? Yes, for example:

@Aspect
@Component
public class HzkAop {
@Before("execution(  * com.hzk.test.CircleService.Aop.ItemService.*(..))")
	public  void  hzkBefore(){
		System.out.println("tx-----");
}
}

Through the above class, we can directly define the method to be represented (through an expression) and the proxy logic (the method modified by @ Before), which is simple and clear. In this way, for Spring, all it needs to do is to parse these annotations, get the corresponding Pointcut object and Advice object after parsing, generate the Advisor object and throw it into ProxyFactory, Then generate the corresponding proxy object. How to parse these annotations is what the * * @ EnableAspectJAutoProxy annotation * * needs to do. I will focus on analyzing the source code in the next article.

[this annotation can also use @ import (annotation awareaspectjautoproxycreator. Class)]

2, Concepts in AOP

We have mentioned the concepts of Advisor, Advice, PointCut and others. First of all, the concept itself in AOP is difficult to understand. It is said on the Spring official website:

Let us begin by defining some central AOP concepts and terminology. These terms are not Spring-specific. Unfortunately, AOP terminology is not particularly intuitive. However, it would be even more confusing if Spring used its own terminology

This means that these concepts in AOP are not unique to Spring. Unfortunately, the concepts in AOP are not particularly intuitive, but if Spring redefines its own, it may lead to more confusion

  1. Aspect: represents the aspect. For example, the class annotated by @ aspect is the aspect. Pointcut, Advice, etc. can be defined in the aspect
  2. Join point: refers to a connection point, which indicates a point in the execution process of a program, such as the execution of a method, such as the handling of an exception. In Spring AOP, a connection point usually represents the execution of a method.
  3. Advice: indicates notification, indicating the action taken at a specific connection point. Advice is divided into different types, which will be discussed in detail later. In many AOP frameworks, including Spring, Interceptor interceptors will be used to implement advice, and an Interceptor chain will be maintained around the connection point
  4. Pointcut: refers to the tangent point, which is used to match one or more connection points. Advice is associated with the tangent point expression, and advice will be executed on the connection point matched with the tangent point expression
  5. Introduction: you can use @ DeclareParents to add an interface to the matched class and specify a default implementation
  6. Target object: target object, represented object
  7. AOP proxy: represents the proxy factory, which is used to create proxy objects. In the Spring Framework, it is either JDK dynamic proxy or CGLIB proxy
  8. Weaving: it refers to weaving, and it refers to the action of creating proxy object. This action can take place during compilation (such as Aspejctj) or runtime, such as Spring AOP

Corresponding API of Advice in Spring AOP

Among the notes in Aspject mentioned above, five are used to define Advice, indicating agent logic and execution timing:

  1. @Before
  2. @AfterReturning
  3. @AfterThrowing
  4. @After
  5. @Around

As I mentioned earlier, Spring itself also provides similar implementation classes for actual execution:

  1. Interface MethodBeforeAdvice inherits the interface BeforeAdvice
  2. Interface AfterReturningAdvice
  3. Interface ThrowsAdvice
  4. Interface AfterAdvice
  5. Interface MethodInterceptor

Spring will parse the five annotations into the corresponding Advice class:

  1. @Before: aspectjmethodbeforeadadvice is actually a method before advice

  2. @After returning: aspectjafterreturning advice is actually an after returning advice

  3. @After throwing: aspectjafterthrowing advice is actually a MethodInterceptor

  4. @After: AspectJAfterAdvice is actually a MethodInterceptor

  5. @Around: aspectja roundadvice is actually a MethodInterceptor

  6. Interface AfterReturningAdvice

  7. Interface ThrowsAdvice

  8. Interface AfterAdvice

  9. Interface MethodInterceptor

In this blog post, you can learn several ways to create agents and the difference between SpringAop and Aspect [these two concepts must be clarified]. In the next chapter, you will conduct an in-depth analysis in combination with the source code.

Topics: Spring