Source code analysis of AOP underlying principle of Spring

Posted by etoast on Tue, 04 Jan 2022 03:58:54 +0100

1, cglib and JDK dynamic proxy

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 a UserService class:

public class UserService  {

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

}

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

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

UserService target = new UserService();

// Through cglib Technology
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);

// Define additional logic, that is, 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("before...");
		Object result = methodProxy.invoke(target, objects);
		System.out.println("after...");
		return result;
	}
}});

// UserService object created by dynamic proxy
UserService userService = (UserService) enhancer.create();

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

The above is the creation of proxy objects through cglib, which is based on parent-child classes. The proxy class (UserService) 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.

In addition to cglib technology, jdk itself provides a dynamic proxy mechanism for creating proxy objects, but it can only have a proxy interface, that is, UserService must have an interface before using jdk dynamic proxy mechanism to generate a proxy object, such as:

public interface UserInterface {
	public void test();
}

public class UserService implements UserInterface {

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

}

Use JDK dynamic proxy to generate a proxy object:

UserService target = new UserService();

// Proxy object for UserInterface interface
Object proxy = Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserInterface.class}, new InvocationHandler() {
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("before...");
		Object result = method.invoke(target, args);
		System.out.println("after...");
		return result;
	}
});

UserInterface userService = (UserInterface) proxy;
userService.test();

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

Exception in thread "main" java.lang.IllegalArgumentException: com.jihu.service.UserService is not an interface

Indicates that it must be an interface. ​

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

2, ProxyFactory

We have introduced two dynamic proxy technologies above. The encapsulated class is called ProxyFactory in Spring, which represents a factory for creating proxy objects. It will be more convenient to use than the above, such as:

UserService target = new UserService();

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvice(new MethodInterceptor() {
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("before...");
		Object result = invocation.proceed();
		System.out.println("after...");
		return result;
	}
});

UserInterface userService = (UserInterface) proxyFactory.getProxy();
userService.test();

This will enhance all methods in UserService.

Through ProxyFactory, we can no longer determine whether to use cglib or jdk dynamic proxy. ProxyFactory will help us judge. If UserService implements the interface, the underlying ProxyFactory will use jdk dynamic proxy. If it does not implement the interface, it will use cglib technology. The above code is because UserService implements the UserInterface interface, So the resulting proxy object is of type UserInterface.

3, Classification of Advice

1. Before Advice: execute before method

2. After returning advice: the method is executed after return

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

4, 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 a UserService class. According to the above example, both methods will be proxied and enhanced. Now we can use Advisor, To control which method a specific agent uses, for example:

UserService target = new UserService();

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisor(new PointcutAdvisor() {
	@Override
	public Pointcut getPointcut() {
		return new StaticMethodMatcherPointcut() {
			@Override
			public boolean matches(Method method, Class<?> targetClass) {
				return method.getName().equals("testAbc");
			}
		};
	}

	@Override
	public Advice getAdvice() {
		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;
			}
		};
	}

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

UserInterface userService = (UserInterface) proxyFactory.getProxy();
userService.test();

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

5, How proxy objects are created

As described above, ProxyFactory, Advisor, Advice, PointCut and other technologies are provided in spring to create 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.

5.1 ProxyFactoryBean

@Bean
public ProxyFactoryBean userServiceProxy(){
	UserService userService = new UserService();

	ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
	proxyFactoryBean.setTarget(userService);
	proxyFactoryBean.addAdvice(new MethodInterceptor() {
		@Override
		public Object invoke(MethodInvocation invocation) throws Throwable {
			System.out.println("before...");
			Object result = invocation.proceed();
			System.out.println("after...");
			return result;
		}
	});
	return proxyFactoryBean;
}

This method is used to define a Bean of UserService, which has gone through 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 UserService as a Bean. ​

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

@Bean
public MethodInterceptor jihuAroundAdvise(){
	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 userService(){
	UserService userService = new UserService();
	
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
	proxyFactoryBean.setTarget(userService);
	proxyFactoryBean.setInterceptorNames("jihuAroundAdvise");
	return proxyFactoryBean;
}

5.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("userSe*");
	beanNameAutoProxyCreator.setInterceptorNames("jihuAroundAdvise");
	beanNameAutoProxyCreator.setProxyTargetClass(true);

    return beanNameAutoProxyCreator;
}

Through BeanNameAutoProxyCreator, you can AOP a batch of beans, 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 you want to proxy according to beanName. ​

5.3 DefaultAdvisorAutoProxyCreator

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

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

    return defaultPointcutAdvisor;
}

@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
	
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

	return defaultAdvisorAutoProxyCreator;
}

The defaultadvisor autoproxycreator 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.

However, we found that in this way, we have to rely on a 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 JihuAspect {

	@Before("execution(public void com.jihu.service.UserService.test())")
	public void jihuBefore(JoinPoint joinPoint) {
		System.out.println("jihuBefore");
	}

}

Through the above class, we directly define the method to be represented (through an expression), And proxy logic (the method modified by @ Before) is simple and clear. For Spring, all it needs to do is to parse these annotations. After parsing, it gets the corresponding Pointcut object and Advice object, generates the Advisor object, throws it into ProxyFactory, and then generates the corresponding proxy object. How to parse these annotations is what the @ EnableAspectJAutoProxy annotation needs to do , which will be analyzed in detail later.

6, Understanding of Spring AOP

OOP stands for object-oriented programming, which is a programming idea. AOP stands for aspect oriented programming, which is also a programming idea. What we described above is the technical support provided by Spring to make it easier for programmers to do aspect oriented programming. In other words, Spring provides a set of mechanisms to make it easier for us to AOP, Therefore, this mechanism can also be called Spring AOP. ​

However, it is worth noting that the annotation provided above defines Pointcut and Advice. Spring is not the first, but AspectJ. Spring not only provides a set of mechanisms to support AOP, but also technologies such as JBoss 4.0 and aspectwerkz provide support for AOP. For the annotation method just mentioned, spring relies on AspectJ. In other words, spring directly takes the annotations defined in AspectJ for use, and does not define them again. However, it only assigns values to the annotation definition. How does spring resolve the specific bottom layer of each annotation, or spring does it itself, so when we use spring, If you want to use @ Before, @ Around and other annotations, you need to introduce aspecj related jar packages separately, such as:

compile group: 'org.aspectj', name: 'aspectjrt', version: '1.9.5'
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.5'

It is worth noting that AspectJ modifies the bytecode during compilation and enhances it directly in the bytecode corresponding to the UserService class, that is, it can be understood that @ Before these annotations will be parsed during compilation, and then the proxy logic will be obtained and added to the bytecode in the proxy class. Therefore, if you want to use AspectJ technology to generate proxy objects, A separate AspectJ compiler is required. We rarely use these annotations in the project. We only use @ Before annotations. During the process of starting Spring, Spring will parse these annotations and use the dynamic proxy mechanism to generate proxy objects. ​

Aspectj used in IDEA: https://blog.csdn.net/gavin_john/article/details/80156963

7, Concepts in AOP

We have mentioned the concepts of Advisor, Advice, PointCut, etc. and some other concepts. First, 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. However, if Spring redefines its own, it may lead to more confusion.

Aspect: represents the aspect. For example, the class annotated by @ aspect is the aspect. Pointcut, Advice, etc. can be defined in the aspect.

Join point: refers to the connection point. It refers to a point during the execution of a program, such as the execution of a method, such as an exception
In Spring AOP, a join point usually represents the execution of a method.

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 are used to implement advice, and an Interceptor chain is maintained around the connection point

Pointcut: represents 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 matching the tangent point expression

Introduction: you can use @ DeclareParents to add an interface to the matched class and specify a default implementation

Target object: target object, proxied object

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

Weaving: it means weaving, and it means the action of creating proxy objects. This action can occur at compile time (such as Aspejctj) or at runtime, such as Spring AOP

8, Advice corresponding API 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 we mentioned earlier, Spring itself provides similar implementation classes to execute the actual implementation:

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 MethodBeforeAdvice
2. @ AfterReturning: AspectJAfterReturningAdvice is actually an AfterReturningAdvice
3. @ after throwing: aspectjafterthrowing advice is actually a MethodInterceptor
4. @ After: AspectJAfterAdvice is actually a MethodInterceptor
5. @ Around: aspectjaroundadadvice is actually a MethodInterceptor

9, Use of TargetSource

In our daily AOP, the proxied object is a Bean object, which is created for us by BeanFactory. However, the Spring AOP provides a TargetSource mechanism that allows us to use custom logic to create the proxied object. ​

For example, when the @ Lazy annotation mentioned earlier is added to the attribute, a proxy object will be assigned to this attribute. The code for generating the proxy object is:

protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
	BeanFactory beanFactory = getBeanFactory();
	Assert.state(beanFactory instanceof DefaultListableBeanFactory,
			"BeanFactory needs to be a DefaultListableBeanFactory");
	final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;

	TargetSource ts = new TargetSource() {
		@Override
		public Class<?> getTargetClass() {
			return descriptor.getDependencyType();
		}
		@Override
		public boolean isStatic() {
			return false;
		}
		@Override
		public Object getTarget() {
			Set<String> autowiredBeanNames = (beanName != null ? new LinkedHashSet<>(1) : null);
			Object target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, null);
			if (target == null) {
				Class<?> type = getTargetClass();
				if (Map.class == type) {
					return Collections.emptyMap();
				}
				else if (List.class == type) {
					return Collections.emptyList();
				}
				else if (Set.class == type || Collection.class == type) {
					return Collections.emptySet();
				}
				throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
						"Optional dependency not present for lazy injection point");
			}
			if (autowiredBeanNames != null) {
				for (String autowiredBeanName : autowiredBeanNames) {
					if (dlbf.containsBean(autowiredBeanName)) {
						dlbf.registerDependentBean(autowiredBeanName, beanName);
					}
				}
			}
			return target;
		}
		@Override
		public void releaseTarget(Object target) {
		}
	};

	ProxyFactory pf = new ProxyFactory();
	pf.setTargetSource(ts);
	Class<?> dependencyType = descriptor.getDependencyType();
	if (dependencyType.isInterface()) {
		pf.addInterface(dependencyType);
	}
	return pf.getProxy(dlbf.getBeanClassLoader());
}

This code uses ProxyFactory to generate proxy objects and TargetSource to get a proxy object in real time by calling the getTarget() method of TargetSource when the proxy object executes a method.

10, Introduction

Introduction to Spring AOP (@ declareparents):
https://www.cnblogs.com/powerwu/articles/5170861.html

11, LoadTimeWeaver

https://www.cnblogs.com/davidwang456/p/5633609.html

Topics: Java Spring