spring-AOP源码浅析
入口
spring aop功能可以使用@EnableAspectJAutoProxy
注解开启,这个注解中,通过@Import(AspectJAutoProxyRegistrar.class)
导入了AspectJAutoProxyRegistrar
这个注册类,该类实现了ImportBeanDefinitionRegistrar
接口,这个接口会在ConfigurationClassPostProcessor
后置处理器中被调用的(具体位置在:postProcessBeanDefinitionRegistry
=> processConfigBeanDefinitions
=> reader.loadBeanDefinitions
=> loadBeanDefinitionsForConfigurationClass
=> loadBeanDefinitionsFromRegistrars
,也就是说它也是在beanFactory后置处理器中执行的,可以用于注册bean定义),这里注册了AnnotationAwareAspectJAutoProxyCreator
这个类;
自动代理创建器
即AnnotationAwareAspectJAutoProxyCreator
类图
可以看到这个类实现了SmartInstantiationAwareBeanPostProcessor
后置处理器接口和Aware
注入接口
功能实现
重写postProcessBeforeInstantiation
方法:
- 根据
advisedBeans
判断是否需要增强,不需要直接跳过,否则继续; - 判断是否是aop基础类,是的话标记到
advisedBeans
,跳出执行,否则继续;判断是否aop有两个方法:isInfrastructureClass
:判断是否通知类(Advice)、切入点类(Pointcut)、切面类(Advisor)、aop基础类(AopInfrastructureBean)shouldSkip
:调用findCandidateAdvisors
方法获取所有候选切面,判断bean名称是否与候选切面相同;
- 判断是否存在
TargetSourceCreator
,如果有则尝试调用getTargetSource
方法获取TargetSource
; - 获取成功则提前创建代理类,并添加到
targetSourcedBeans
,否则什么也不做;
重写postProcessAfterInitialization
方法:
- 根据
earlyProxyReferences
判断是否已经处理(当存在循环依赖时,会在getEarlyBeanReference
方法中提前完成增强),是则直接返回,否则调用wrapIfNecessary
方法增强对象并返回;
重写getEarlyBeanReference
方法:当有循环依赖时,会在实例化完成之前调用这个方法:
- 将bean缓存到
earlyProxyReferences
; - 调用
wrapIfNecessary
方法提前增强目标对象;
wrapIfNecessary
方法执行流程:
- 判断
targetSourcedBeans
,即是否已经代理,没有则继续; - 根据
advisedBeans
判断是否需要增强,需要则继续; - 判断是否aop基础类,是则标记到
advisedBeans
,否则继续; - 调用
getAdvicesAndAdvisorsForBean
方法获取通知方法(这个方法又是调用findEligibleAdvisors
方法实现的)- 查找所有候选切面;
- 从候选切面中获取满足条件的切面;
- 如果切面列表不为空,则排序;
- 如果获取到的通知方法为空,则标记到
advisedBeans
为false,否则标记为true,然后继续; - 调用
createProxy
方法,先创建ProxyFactory
,设置通知方法,然后创建并返回代理对象;
切面获取流程
了解了代理工厂的用法,这里再简单分析下,查找候选切面、查找满足条件的切面以及创建proxyFactory的过程;
查找所有候选切面(org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
):这个方法只会在首次调用时查找和创建切面并缓存到map中,后续调用就直接在map中取,这里主要看第一次调用:
- 从容器中获取所有bean,遍历所有bean,做后续的处理:
- 判断:有
Aspect
注解,并且没有被ajc编译; - 将切面类封装到
AspectMetadata
; - 判断代理类型是否单例,这里只考虑单例;
- 创建
BeanFactoryAspectInstanceFactory
; - 调用
this.advisorFactory.getAdvisors
方法创建切面;- 获取类型、名称;校验
Aspect
注解、ajc、切面类型; - 将
BeanFactoryAspectInstanceFactory
包装到LazySingletonAspectInstanceFactoryDecorator
,使得每个切面实例只实例化一次; - 获取所有方法(排除掉
Pointcut
注解修饰的方法,然后按Around
、Before
、After
、AfterReturning
、AfterThrowing
注解顺序排序) - 根据方法注解创建
AspectJExpressionPointcut
切入点; - 创建
InstantiationModelAwarePointcutAdvisorImpl
切面,这是PointcutAdvisor
的一个子接口,添加了懒加载的功能; - 处理
DeclareParents
注解修饰方法的切面; - 将创建的切面缓存到map中;
- 获取类型、名称;校验
查找满足条件的切面(org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply
):
- 通过
ThreadLocal
保存当前正在操作的Bean; - 调用
AopUtils
工具类的findAdvisorsThatCanApply
方法判断是否满足增强条件: - 先判断
IntroductionAdvisor
类型的切面,调用matches方法,满足条件的引介切面; - 再判断
PointcutAdvisor
:获取ClassFilter
,调用matches方法判断是否满足条件,如果满足则继续判断; - 获取
MethodMatcher
,遍历所有class和实现了的接口,调用matches方法判断是否满足条件; - 最后移除
ThreadLocal
中保存的变量;
如果找到的满足条件的切面不为空,spring默认会在切面列表的第一个位置插入一个增强为ExposeInvocationInterceptor
的DefaultPointcutAdvisor
切面,
ProxyFactory
在上面的分析中,可以知道AOP最终是要获取通知,然后通过ProxyFactory
这个来创建增强对象的(这个类最终还是调用的jdk或者cglib的方法来创建的代理类,两个实现类分别对应JdkDynamicAopProxy
和
CglibAopProxy
,需要的时候可以借鉴下里面的代码,这里不细讨论),这里先了解下代理工厂结合通知、切面的简单用法;
通知
Advice
接口标记的接口,它有下面这些子接口:
MethodBeforeAdvice
:前置通知;ThrowsAdvice
:异常通知;AfterReturningAdvice
:返回通知;MethodInterceptor
:可以简单理解为环绕通知;
用法示例:
package com.demo.core.spring;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
class AdviceTest {
@Test
void test() {
ProxyFactory proxyFactory = new ProxyFactory(new A());
// 前置通知
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("======> before ....");
}
});
// 异常通知
proxyFactory.addAdvice(new CustomThrowAdvice());
// 返回通知
proxyFactory.addAdvice(new AfterReturningAdvice() {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("======> after returning ...");
}
});
// 可以理解为环绕通知
proxyFactory.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("======> method interceptor ...");
return invocation.proceed();
}
});
A proxy = (A) proxyFactory.getProxy();
proxy.doSth("参数....");
System.out.println("\n==================下面这个方法会抛异常================\n");
try {
proxy.doSth(null);
} catch (Exception e) {
Assertions.assertTrue(ClassUtils.isAssignable(RuntimeException.class, e.getClass()));
System.out.println(e.getMessage());
}
}
/**
* 异常通知方法是通过反射调用的,不能定义为匿名内部类
*/
public static class CustomThrowAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
System.out.println("======> after throw ... ");
}
}
public static class A {
public String doSth(String param) {
System.out.println("===> do sth: " + param);
if (param == null) {
throw new RuntimeException("随便抛个异常");
}
return "返回值...";
}
}
}
切面
Advisor
接口标记的接口,由切入点加通知组成,它有下面这些子接口:
IntroductionAdvisor
:引介切面,通过matches方法判断是否需要增强类,可以做到直接代理接口,不需要实现类;PointcutAdvisor
:切入点切面,根据指定规则判断是否需要增强类,具体的规则一般有:NameMatchMethodPointcut
:通过方法名精确匹配;ControlFlowPointcut
:根据当前线程的堆栈信息确定是否切入(效率低);ComposablePointcut
:组合模式;JdkRegexpMethodPointcut
:正则匹配;AspectJExpressionPointcut
:切点表达式匹配;
用法示例:
package com.demo.core.spring;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.Test;
import org.springframework.aop.*;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;
class AdvisorTest {
@Test
void introduction() {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(new CustomIntroductionAdvisor(new IntroductionInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("======> introduction interceptor ...... ");
if (invocation.getThis() == null) {
Method method = invocation.getMethod();
return String.format("%s#%s %s(%s)", method.getDeclaringClass().getSimpleName(),
method.getReturnType().getSimpleName(), method.getName(),
StringUtils.collectionToDelimitedString(
Arrays.stream(invocation.getArguments()).collect(Collectors.toList()),
",", "\"", "\""));
}
return invocation.proceed();
}
@Override
public boolean implementsInterface(Class<?> intf) {
return false;
}
}));
I1 i1 = (I1) proxyFactory.getProxy();
System.out.println("=========> " + i1.doSth("i1 param ...."));
I2 i2 = (I2) proxyFactory.getProxy();
// 随手一写发现个问题,这里的代理配置相当于:创建代理类同时实现I1、I2两个接口,这两个接口刚好有相同签名的方法;
// 那么这里默认只实现第一个接口的同签名方法,所以调用i2.doSth方法时,打印的是i1的信息;
// 如果这两个方法只有返回值不一样,则会报错,这一点和Java语法是一致的;
System.out.println("=========> " + i2.doSth("i2 param ...."));
System.out.println("=========> " + i2.doSth("i2 param1 ....", "i2 param2 ...."));
}
@Test
void pointcut() {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(new CustomPointcutAdvisor(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("======> pointcut advisor before ....");
}
}));
proxyFactory.setTarget(new A());
A a = (A) proxyFactory.getProxy();
System.out.println("=========> " + a.doSth("param a..."));
System.out.println("===> b");
proxyFactory.setTarget(new B());
B b = (B) proxyFactory.getProxy();
System.out.println("=========> " + b.doSth("param b..."));
}
/**
* 引介切面
*/
public static class CustomIntroductionAdvisor implements IntroductionAdvisor, ClassFilter {
private final Advice advice;
public CustomIntroductionAdvisor(Advice advice) {
this.advice = advice;
}
@Override
public boolean matches(Class<?> clazz) {
// 这个方法返回false,则不增强这个class
return ClassUtils.isAssignable(I1.class, clazz) || ClassUtils.isAssignable(I2.class, clazz);
}
@Override
public ClassFilter getClassFilter() {
return this;
}
@Override
public void validateInterfaces() throws IllegalArgumentException {
}
@Override
public Advice getAdvice() {
return advice;
}
@Override
public boolean isPerInstance() {
return true;
}
@Override
public Class<?>[] getInterfaces() {
return new Class[]{I1.class, I2.class};
}
}
/**
* 切入点切面
*/
public static class CustomPointcutAdvisor implements PointcutAdvisor {
private final Advice advice;
public CustomPointcutAdvisor(Advice advice) {
this.advice = advice;
}
@Override
public Pointcut getPointcut() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(public * com.demo.core.spring.AdvisorTest$B.*(..))");
return pointcut;
}
@Override
public Advice getAdvice() {
return this.advice;
}
@Override
public boolean isPerInstance() {
return false;
}
}
public interface I1 {
String doSth(String param);
}
public interface I2 {
String doSth(String param);
String doSth(String param1, String param2);
}
public static class A {
public String doSth(String param) {
System.out.println("=========> do sth: " + param);
return "a...";
}
}
public static class B {
public String doSth(String param) {
System.out.println("=========> do sth: " + param);
return "b...";
}
}
}
执行过程
ProxyFactory
的执行过程要分情况,当frozen
为true
时,走FixedChainStaticTargetInterceptor
,为false
时,走DynamicAdvisedInterceptor
,这两个类都实现了org.springframework.cglib.proxy.MethodInterceptor
接口,这里以false情况为例做简单分析;
DynamicAdvisedInterceptor
:是CglibAopProxy
的私有静态内部类,其AdvisedSupport
成员变量保存切面相关信息;还实现了MethodInterceptor
接口的intercept
方法,这个方法是执行的入口,其执行大致流程如下:
- 获取目标对象和class类型;
- 获取执行链(就是排序好的包装了通知方法的和目标方法的
MethodInterceptor
的子接口的list集合); - 创建
CglibMethodInvocation
(这里会判断:是非Object派生方法的共有方法),然后执行proceed
方法; proceed
方法会被递归调用,直到执行链执行结束(这里并不是按执行链依次调用,而是在执行每个执行链节点方法中的适当位置调用proceed
方法,这个方法中会根据currentInterceptorIndex
的值从执行链list中去对应的执行节点后自增,直到超过list大小,调用invokeJoinpoint
方法,具体调用顺序为:before -> throws -> after return -> method interceptor -> invokeJoinpoint -> 目标方法
);