Spring AOP overview
Recently, when looking at the source code of seata, the api of spring aop can be seen everywhere. So while looking at seata, I summarized spring aop
When using the Spring framework, we often need to deal with the two major features of Spring, IOC and AOP. This article will then share the underlying implementation of AOP. The basic content will not be introduced in this article, mainly focusing on the design concept of the underlying api
"Common concepts of AOP design concept are as follows"
"The main application scenarios of AOP are as follows"
"The implementation of Spring AOP has mainly experienced two generations"
The first generation: spring1.x version, which implements the functions of AOP. The second generation: spring2.x version, Spring integrates the implementation of AspectJ
Spring AOP generation
"When we want to add crosscutting logic based on ready-made implementation, we first need to find out where to enhance it. Let's use Pointcut to filter it."
First write a Service to facilitate the later demonstration
public interface EchoService { String echo(String message); }
public class DefaultEchoService implements EchoService { @Override public String echo(String message) { return message; } }
Pointcut
Pointcut interface is defined as follows
public interface Pointcut { // Filter by class ClassFilter getClassFilter(); // Filter by method MethodMatcher getMethodMatcher(); Pointcut TRUE = TruePointcut.INSTANCE; }
"When we want to filter out the echo methods of EchoService, we can define the following Pointcut"
public class EchoPointcut implements Pointcut { @Override public ClassFilter getClassFilter() { return new ClassFilter() { @Override public boolean matches(Class<?> clazz) { return EchoService.class.isAssignableFrom(clazz); } }; } @Override public MethodMatcher getMethodMatcher() { return new MethodMatcher() { @Override public boolean matches(Method method, Class<?> targetClass) { return "echo".equals(method.getName()) && method.getParameterTypes().length == 1 && Objects.equals(String.class, method.getParameterTypes()[0]); } @Override public boolean isRuntime() { return false; } @Override public boolean matches(Method method, Class<?> targetClass, Object... args) { return false; } }; } }
It still seems troublesome, so Spring has many built-in implementations. Generally, we can use the built-in implementations without defining them ourselves. The above filtering process can be changed to the following
// The method name is echo Will be intercepted NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); pointcut.setMappedName("echo");
Some pointcuts provided by Spring are implemented as follows
Jointpoint
"The place to add crosscutting logic filtered by Pointcut is Jointpoint." In the AOP concept, crosscutting logic can be added in many places, such as method execution, field setting, etc. However, "Spring only supports method execution as a Joinpoint", because this type of Joinpoint basically meets 80% of the scenarios
In Joinpoint type "Method call is better than method execution"
Because Spring only supports method execution, we can get enhanced method information from the Joinpoint implementation class
Advice
When jointpoints are selected, we need to add crosscutting logic on these jointpoints, which is called Advice
There are two ways to implement crosscutting logic in Spring
-
Implement the Advice interface
-
Implement the IntroductionInfo interface
The method of implementing the Advice interface is the most commonly used, which will be analyzed in detail later. The way to implement the IntroductionInfo interface is basically useless. Here is a demonstration of the specific usage to facilitate understanding the design concept of the whole AOP API
"IntroductionInfo mainly adds new functions by implementing specific interfaces for the target class"
public interface SayName { String getName(); }
public class DefaultSayName implements SayName { @Override public String getName() { return "I am service"; } }
public static void main(String[] args) { SayName sayName = new DefaultSayName(); EchoService echoService = new DefaultEchoService(); // Built in implementation of IntroductionInfo interface DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(sayName); Advisor advisor = new DefaultIntroductionAdvisor(interceptor, SayName.class); ProxyFactory proxyFactory = new ProxyFactory(echoService); proxyFactory.addAdvisor(advisor); // hello world EchoService proxyService = (EchoService) proxyFactory.getProxy(); System.out.println(proxyService.echo("hello world")); // I am service SayName proxySayName = (SayName) proxyFactory.getProxy(); System.out.println(proxySayName.getName()); }
Maybe you are unfamiliar with Advisor and ProxyFactory in this example. You don't know what role they play. Don't worry. We will analyze the roles of these two classes in detail later
"The way to implement the Advice interface should be the most common way in the Spring AOP generation"
"Add pre execution crosscutting logic to the put method of HashMap" and print the values of key and value put into HashMap
public static void main(String[] args) { JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); pointcut.setPattern(".*put.*"); DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(); advisor.setPointcut(pointcut); advisor.setAdvice(new MethodBeforeAdvice() { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.printf("Currently stored key by %s,Value is %s", args[0], args[1]); } }); ProxyFactory proxyFactory = new ProxyFactory(new HashMap()); proxyFactory.addAdvisor(advisor); Map<String, String> proxyMap = (Map<String, String>) proxyFactory.getProxy(); // The currently stored key is a. Value is a proxyMap.put("a", "a"); }
Advisor
As we said earlier, in the AOP design concept, we use Aspect to declare aspects, and each Aspect can contain multiple pointcuts and Advice.
"In the Spring AOP generation, the corresponding implementation of Aspect is Advisor.". That is, Advisor is the container of Pointcut and Advice, but an Advisor can only contain one Pointcut and Advice
Because there are two types of implementation methods of Advice, the corresponding Advisor can also be divided into two types
Weave in
"The process of weaving Advice into Jointpoint in Spring is realized through dynamic proxy.". Of course, there are many ways to weave in, not just dynamic agents
Spring uses jdk dynamic proxy and cglib to implement dynamic proxy. Factory mode is used to generate proxy objects. It can be clearly seen from the api
"jdk dynamic agent"
public class CostInvocationHandler implements InvocationHandler { private Object target; public CostInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); Object result = method.invoke(target, args); long cost = System.currentTimeMillis() - startTime; System.out.println("cost " + cost); return result; } }
public static void main(String[] args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{EchoService.class}, new CostInvocationHandler(new DefaultEchoService())); EchoService echoService = (EchoService) proxy; // cost 0 // hello world System.out.println(echoService.echo("hello world")); }
「cglib」
public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(DefaultEchoService.class); enhancer.setInterfaces(new Class[] {EchoService.class}); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object source, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { long startTime = System.currentTimeMillis(); Object result = methodProxy.invokeSuper(source, args); long cost = System.currentTimeMillis() - startTime; System.out.println("cost " + cost); return result; } }); EchoService echoService = (EchoService) enhancer.create(); // cost 29 // hello world System.out.println(echoService.echo("hello world")); }
Automatic dynamic proxy for Spring AOP
We have been demonstrating it in the form of API above. Of course, we can also put these objects into the Spring container, let Spring manage them, and generate proxy objects for beans in the Spring container
The above Demo can be changed to the following form, with little change
"Manual configuration"
public class ProxyConfig { // Create proxy object @Bean public EchoService echoService() { return new DefaultEchoService(); } // Create advice @Bean public CostMethodInterceptor costInterceptor() { return new CostMethodInterceptor(); } // Creating an advisor using pointcut and advice @Bean public Advisor advisor() { NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(); advisor.setMappedName("echo"); advisor.setAdvice(costInterceptor()); return advisor; } // Create proxy object @Bean("echoProxy") public ProxyFactoryBean proxyFactoryBean(EchoService echoService) { ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setTarget(echoService); proxyFactoryBean.setInterceptorNames("advisor"); return proxyFactoryBean; } }
public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProxyConfig.class); // Get proxy object EchoService echoService = (EchoService) context.getBean("echoProxy"); // cost 0 // hello world System.out.println(echoService.echo("hello world")); }
"You can see that we need to configure the corresponding ProxyFactoryBean for each generated proxy object, and then obtain the proxy object from the container for use.". When there are few proxy objects, they can cope with it. When there are many proxy objects, they must not be tired enough to vomit blood. Is there any simple way?
Spring must have thought of this problem, so it provides the following class defaultadvisor autoproxycreator to implement automatic proxy. We can put this class into the spring container, as shown below
"Auto configuration"
public class AutoProxyConfig { // Create proxy object @Bean public EchoService echoService() { return new DefaultEchoService(); } // Create advice @Bean public CostMethodInterceptor costInterceptor() { return new CostMethodInterceptor(); } // Creating an advisor using pointcut and advice @Bean public Advisor advisor() { NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(); advisor.setMappedName("echo"); advisor.setAdvice(costInterceptor()); return advisor; } @Bean public DefaultAdvisorAutoProxyCreator autoProxyCreator() { return new DefaultAdvisorAutoProxyCreator(); } }
public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutoProxyConfig.class); EchoService echoService = context.getBean(EchoService.class); // cost 0 // hello world System.out.println(echoService.echo("hello world")); }
The object obtained from the container is directly the object after being proxied, which is very convenient. "Spring AOP provides many classes to implement automatic proxy, but they have a common parent class AbstractAutoProxyCreator. It seems that the secret of automatic proxy lies in this AbstractAutoProxyCreator class."
Implementation of Spring AOP automatic dynamic proxy
What would you do if you were to implement automatic proxy for objects?
Of course, it is smart to intervene in the Bean declaration cycle through BeanPostProcessor! That's what Spring did to test our ideas
Looking at the inheritance relationship of this class basically verifies our idea. We just need to see what methods he has rewritten the BeanPostProcessor?
"AbstractAutoProxyCreator rewrites the following two important methods" postprocessbeforeinstance (executed before Bean instantiation) and postProcessAfterInitialization (executed after Bean initialization)
"postProcessBeforeInstantiation (execution stage before Bean instantiation)"
When the user customizes the implementation of TargetSource, the target object generation agent will be obtained from TargetSource. However, in general, we rarely customize the implementation of TargetSource. So this part is no longer analyzed. Look directly at postProcessAfterInitialization
"postProcessAfterInitialization (Bean post initialization stage execution)"
If it is not proxied, it will enter the wrapIfNecessary method
The idea is very simple, that is, get the corresponding Advisor according to the Bean, then create its proxy object and return it.
"So when the interviewer asks you how Spring AOP and IOC are combined, do you know how to answer?"
In the post initialization stage of the Bean life cycle, if the Bean needs to add crosscutting logic, the corresponding proxy object will be generated at this stage
Spring AOP generation 2 (integrated with AspectJ)
After the release of Spring 2.0, a new way of using spring AOP is added. Spring AOP integrates AspectJ. This version of spring AOP is most commonly used
The main changes are as follows
-
POJO can be used to define Aspect and Adivce, and provide a series of corresponding annotations, such as @ Aspect and @ Around. Instead of implementing the corresponding interface in version 1.x
-
We all know how to support the expression of pointcut in aspectj
Demonstrate how aop is used in version 2.0
Define section
@Aspect public class AspectDefine { @Pointcut("execution(* com.javashitang.proxy.EchoService.echo(..))") public void pointcutName() {} @Around("pointcutName()") public Object calCost(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = joinPoint.proceed(); long cost = System.currentTimeMillis() - startTime; System.out.println("cost " + cost); return result; } @Before("pointcutName()") public void beforeMethod() { System.out.println("beforeMethod"); } }
Add configuration and inject implementation classes
@EnableAspectJAutoProxy public class AspectJConfig { @Bean public EchoService echoService() { return new DefaultEchoService(); } }
public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AspectJConfig.class, AspectDefine.class); EchoService echoService = context.getBean(EchoService.class); // beforeMethod // cost 0 // hello world System.out.println(echoService.echo("hello world")); context.close(); }
"Although AspectJ was integrated into spring aop after spring 2.0, it was only used as AspectJ's" fur coat "because the underlying implementation and weaving method was still the original implementation system of 1.x."
@What's the use of EnableAspectJAutoProxy?
"When we want to use aop version 2.0, we must add the @ EnableAspectJAutoProxy annotation on the configuration class. What is the role of this annotation?"
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false; }
You can see a very important sentence
@Import(AspectJAutoProxyRegistrar.class)
Inject beans through @ Import. There are three ways to inject beans through @ Import annotation
-
Based on Configuration Class
-
Based on ImportSelector interface
-
Based on ImportBeanDefinitionRegistrar interface
This code mainly does two things
-
Inject annotation awareaspectjautoproxycreator into the container
-
When the proxyTargetClass or exposeProxy attribute in the @ EnableAspectJAutoProxy annotation is true, change the proxyTargetClass or exposeProxy attribute in the AnnotationAwareAspectJAutoProxyCreator to true
"proxyTargetClass and exposeProxy are saved in the parent class ProxyConfig of the AnnotationAwareAspectJAutoProxyCreator class. This class stores some configurations to control the generation process of proxy objects."
proxyTargetClass: true use CGLIB to create proxy based on class; false use java interface to create proxy exposeProxy: true save proxy object in AopContext, otherwise it will not be saved
The first attribute is easy to understand, so what is the function of the second attribute? Demonstrate it
@Service public class SaveSevice { public void method1() { System.out.println("method1 executed"); method2(); } public void method2() { System.out.println("method2 executed"); } }
@Aspect public class AspectDefine { @Pointcut("execution(* com.javashitang.invalid.SaveSevice.method2(..))") public void pointcutName() {} @Around("pointcutName()") public Object calCost(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Open transaction"); return joinPoint.proceed(); } }
@EnableAspectJAutoProxy public class InvalidDemo { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SaveSevice.class, AspectDefine.class, InvalidDemo.class); SaveSevice saveSevice = context.getBean(SaveSevice.class); saveSevice.method1(); System.out.println("--"); saveSevice.method2(); } }
The result is
method1 executed method2 executed -- Open transaction method2 executed
"It can be seen that when method2 is called through method1, aop does not take effect. Only when method2 is called directly, aop takes effect. This is the reason why the transaction method is invalid since the call is not a proxy object's method."
There are many solutions, such as re taking proxy objects from ApplicationContext and calling proxy objects. The other is to get proxy objects through AopContext, and the principle is to put proxy objects in ThreadLocal when the method is called.
@Service public class SaveSevice { public void method1() { System.out.println("method1 executed"); ((SaveSevice) AopContext.currentProxy()).method2(); } public void method2() { System.out.println("method2 executed"); } }
Change the exposeProxy property to true
@EnableAspectJAutoProxy(exposeProxy = true)
method1 executed Open transaction method2 executed -- Open transaction method2 executed
It can be seen that aop takes effect successfully. "When you use @ Transactional annotation and distributed transaction framework, you must pay attention to the problem of sub call, otherwise it is easy to cause transaction failure."
Let's talk about injecting AnnotationAwareAspectJAutoProxyCreator into the container, so what's the role of this class?
See if the inheritance relationship is very similar to the defaultadvisor autoproxycreator class we analyzed above. Isn't this to enable automatic proxy?
Forget about the implementation of automatic proxy? Look back
Tangent expression
"Spring AOP uses AspectJExpressionPointcut to bridge the filtering ability of Aspect. In fact, Aspect has many types of pointcut expressions, but spring AOP only supports the following 10 kinds, because Aspect supports many types of joinpoints, but spring AOP only supports the method execution of this kind of JoinPoint, so the rest of the expressions are unnecessary.
Because the expressions provided by AspectJ are often used in our work, we will demonstrate the specific usage in combination with the Demo
Expression type | explain |
---|---|
execution | Match method expression, preferred method |
within | Qualified type |
this | The proxy object is of the specified type, and all methods will be intercepted |
target | The target object is of the specified type, and all methods will be intercepted |
args | Parameters in matching method |
@target | The target object has the specified annotation, and all methods will be intercepted |
@args | There is a specified annotation on the type of the method parameter |
@within | If there is a specified annotation on the calling object, all methods will be intercepted |
@annotation | Method with specified annotation |
「execution」
Match method expression, preferred method
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
The pointcut expression for intercepting the perform method of the Performance class is as follows
Put some official demos
// The execution of any public method: execution(public * *(..)) // The execution of any method with a name that begins with set execution(* set*(..)) // The execution of any method defined by the AccountService interface execution(* com.xyz.service.AccountService.*(..)) // The execution of any method defined in the service package: execution(* com.xyz.service.*.*(..))
"within" qualified type
// Intercept any method of any class in the service package within(com.xyz.service.*) // Any method of intercepting any class in service package and sub package within(com.xyz.service..*)
「this」
The proxy object is of the specified type, and all methods will be intercepted
Give an example
@Configuration @EnableAspectJAutoProxy public class ThisDemo { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ThisDemo.class, AspectDefine.class); Name name = context.getBean(Name.class); name.getName(); System.out.println(name instanceof Student); } @Aspect public class AspectDefine { @Before("this(com.javashitang.aspectjPointcut.thisDemo.ThisDemo.Student)") public void before() { System.out.println("before"); } } @Bean public Student student() { return new Student(); } public class Student implements Name { @Override public String getName() { return null; } } public interface Name { String getName(); } }
Output as
false
jdk dynamic Proxy will be used when there is an interface, so the Proxy object is Proxy and will not be intercepted
When it is set to jdk, the dynamic proxy object is Student, which is intercepted normally
Change the annotation to the following form @ EnableAspectJAutoProxy(proxyTargetClass = true)
Output as
before true
The "target" target object is the specified type, and all methods will be intercepted
// If the target object is of type AccountService, it will be delegated target(com.xyz.service.AccountService)
The difference between this and target is that "this acts on the proxy object and target acts on the target object"
args matches the parameters in the method
// There is only one parameter matching and the type is com.ms.aop.args.demo1.UserModel @Pointcut("args(com.ms.aop.args.demo1.UserModel)") // Match multiple parameters args(type1,type2,typeN) // Match all methods with the first parameter type com.ms.aop.args.demo1.UserModel, .. Represents any parameter @Pointcut("args(com.ms.aop.args.demo1.UserModel,..)")
The "@ target" target object has the specified annotation. All methods will be blocked
// The target object contains com.ms.aop.jtarget.Annotation1 annotation. Any method calling the target object will be intercepted @target(com.ms.aop.jtarget.Annotation1)
There is a specified annotation on the type of the "@ args" method parameter
// Match one parameter, and the class to which the first parameter belongs has Anno1 annotation @args(com.ms.aop.jargs.demo1.Anno1) // Multiple parameters are matched, and the types of multiple parameters have specified annotations @args(com.ms.aop.jargs.demo1.Anno1,com.ms.aop.jargs.demo1.Anno2) // Multiple parameters are matched, and the class to which the first parameter belongs has Anno1 annotation @args(com.ms.aop.jargs.demo2.Anno1,...)
「@within」
If there is a specified annotation on the calling object, all methods will be intercepted
// All methods in classes that declare com.ms.aop.jwithin.Annotation1 annotations will be intercepted @within(com.ms.aop.jwithin.Annotation1)
"@ target is different from @ within" @ target focuses on the called object, @ within focuses on the called object
"@ annotation" has a method for specifying annotations
// Annotation1 annotation on the called method @annotation(com.ms.aop.jannotation.demo2.Annotation1)
Sequential relationship between Adivce
When a method is intercepted by an aspect class, the execution sequence is as follows
@Around - > @ before - > method execution - > @ around - > @ after - > @ afterreturning / @ afterthrowing
When the method ends normally, @ AfterReturning is executed. When the method ends abnormally, @ AfterThrowing is executed. They will not be executed at the same time
When a method is intercepted by multiple aspect classes, the execution sequence is as follows
"The execution Order of multiple aspect s can be controlled through @ Order annotation or implementation of Oreder interface"
"The order of Adivce must be sorted out clearly, otherwise you don't know how many magical behaviors sometimes happen."