最近在做代码的性能优化,用arthas的trace命令来查看方法调用链及各个方法的耗时,发现一个加了事务注解(@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED))的方法执行速度有点慢,调用此方法(代理)的执行时间几乎和内部方法的执行时间一样了,去掉方法上的事务注解,耗时就很少了,虽然后续还不知道如何处理这个方法(方法上的事务是后来加上去的,应该是不会去掉了,很有可能要改方法外的逻辑),但是对这点挺好奇的,于是查了下spring aop动态代理的文章,这里做个记录。
原文链接:https://yaoyuanyy.github.io/categories/spring/
01 AnnotationAwareAspectJAutoProxyCreator
直接看EnableAspectJAutoProxy接口注解(注释还是有点东西的),关键点在于@Import的类上。
1 | /** |
下面看下AspectJAutoProxyRegistrar这个类
1 | /** |
最终,AnnotationAwareAspectJAutoProxyCreator bean被创建后放入beanFactory。后面当其他类bean instance时会用到它,为什么会用到它呢?核心关键点:因为AnnotationAwareAspectJAutoProxyCreator
是一个InstantiationAwareBeanPostProcessor和BeanPostProcessor,所以beanFactory容器中所有的类被创建都会经过他的的实例化前后置处理和初始化前后置处理。而AnnotationAwareAspectJAutoProxyCreator从父类AbstractAutoProxyCreator继承来的postProcessBeforeInstantiation(Class<?> beanClass, String beanName)方法和postProcessAfterInitialization(Object bean, String beanName)方法都是生成代理类的功能。
具体调用流程是在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])中调用的
1 | try { |
两个方法生成代理类的时机不同,过滤条件也不一样,下面详细说下
postProcessBeforeInstantiation实例化前置方法
1 | public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { |
postProcessAfterInitialization初始化后置方法
1 | /** |
02 判断beanName或beanClass是否生成Proxy代理类
首先需要创建两个类,然后启动项目debug进行分析
1 |
|
下面这个是一个切面类
1 |
|
断点走到postProcessBeforeInstantiation()中,进入到shouldSkip(beanClass, beanName)方法
1 | protected boolean shouldSkip(Class<?> beanClass, String beanName) { |
首先获取advisor候选者列表,然后遍历,如果有AspectName等于beanName,说明这个beanName是Aspect,不用生成代理,方法返回true,否则走父类逻辑(返回false)。查看获取advisor候选者方法findCandidateAdvisors()
1 | protected List<Advisor> findCandidateAdvisors() { |
方法内部逻辑很简单,但是意义重大,从两个角度获取Advisor。角度一是从beanFactory容器中获取Advisor类;角度二是从beanFactory容器中获取Aspect类。先看其一
1 | public List<Advisor> findAdvisorBeans() { |
这里只列出了关键代码,可以看到,从beanFactory容器中获取Advisor的子类name集合,然后验证下name是否合格,合格后获取name对应的bean。但是实际debug代码时,这步却没有获取到Advisor的子类,所以返回的是空list。
回到findCandidateAdvisors()方法,看其二
1 | public List<Advisor> buildAspectJAdvisors() { |
代码(2)处,判断beanType是否为Aspect,实现逻辑为AnnotationUtils.findAnnotation(clazz, Aspect.class) != null,开头我定义了一个TimeFeeAspect切面类,所以这里只有TimeFeeAspect符合条件,进入if块里面
代码(3)处,构造一个带有Aspect metadata和beanFactory的BeanFactoryAspectInstanceFactory实例,传给关键代码(4)块,看代码(4)内部逻辑
1 | public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) { |
这个方法的目的很明确:获取advisor。方法解析出入参aspectClass的methods,遍历methods从而获取advisor集合,advisor包含advice和pointcut。
根据method获取advisor的getAdvisor(..)方法就是核心逻辑了,看其内部
1 | public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, |
方法首先验证后,根据adviceMethod和aspectClass生成PointCut,然后构造成InstantiationModelAwarePointcutAdvisorImpl实例。首先看生成PointCut的逻辑
1 | private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) { |
生成PointCut后,代码回到ReflectiveAspectJAdvisorFactory.getAdvisor()方法的构造InstantiationModelAwarePointcutAdvisorImpl实例部分,查看其构造方法
1 | public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, |
可以看到,InstantiationModelAwarePointcutAdvisorImpl类包含AspectJExpressionPointcut类型的pointcut名属性,adviceName,aspectJAdviceMethod切面方法等属性。同时也会根据advice的type生成对应的xxxAdvice对象。看最后一行instantiatedAdvice的解析代码
1 | private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) { |
整个方法就是为了获取Advice,根据切面方法(即TimeFeeAspect.doTimeFeeIntercepter)上的advice注解type返回对应的xxxxAdvice实例。这里doTimeFeeIntercepter方法上注解了@Before advice,所以我们得到是AspectJMethodBeforeAdvice对象。AspectJMethodBeforeAdvice包含advice method, pointcut, aspect class name。AspectJMethodBeforeAdvice相当于interceptor。当生成proxy代理类时,AspectJMethodBeforeAdvice就是proxy代理类的interceptor,proxy的切入的方法就是AspectJMethodBeforeAdvice的advice method,即TimeFeeAspect.doTimeFeeIntercepter方法。这样,就把目标类的被切入方法和aspect的想切入的方法关联起来了。所以每次程序走到目标类的被切入方法时,都会先走aspect的切入的方法。
所以,对外暴露的advisor其实就是InstantiationModelAwarePointcutAdvisorImpl对象,当生成proxy代理类的时候传进去的advisor也就是InstantiationModelAwarePointcutAdvisorImpl对象了。
到这,Advice class获取到了,Advice的PointCut也拿到了,但是PointCut自身的表达式(Expression)值还没有拿到。接着走代码
回到postProcessBeforeInstantiation()方法,由于我们找到了Advisor,所以shouldSkip(beanClass, beanName)返回false,逻辑往下走。又由于getCustomTargetSource(beanClass, beanName)返回null,所以这步没有生成入参beanName或beanClass的代理
这样,postProcessBeforeInstantiatioin()方法的逻辑就走完了。但是beanName或beanClass还有一次生成代理类的机会,就是postProcessAfterInitialization()方法提供的,可以看到:wrapIfNecessary方法是核心,查看其内部代码
1 | protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { |
可以看到,方法上半部分与postProcessBeforeInstantiation()方法相同,刚才的分析就是shouldSkip(bean.getClass(), beanName)方法,所以我们直接看getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null)。这个方法获取Advisor,看其内部逻辑
1 | protected Object[] getAdvicesAndAdvisorsForBean( |
findEligibleAdvisors()方法首先获取Advisor候选者(findCandidateAdvisors()的逻辑在postProcessBeforeInstantiation()方法分析时已经说过),然后通过findAdvisorsThatCanApply()方法检验这些Advisor候选者对beanClass来说是否是合格的Advisor。这里就很关键了,我们看其内部代码
1 | protected List<Advisor> findAdvisorsThatCanApply( |
方法重点是条件判断。这里只列出部分代码,其实方法将候选者Advisor分为两拨,一拨是IntroductionAdvisor,然后条件判断canApply(candidate, clazz);另一拨是其他Advisor,然后条件判断canApply(candidate, clazz, hasIntroductions),我们debug时走的后一个条件判断方法逻辑,看其内部代码逻辑
1 | public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) { |
整个方法都在判断targerClass是否匹配这个pointcut,由于此时的pointcut.pointcutExpression还没有赋值呢,所以在判断匹配之前,需要pointcut.pointcutExpression赋值,所以整个AopUtis.canApply分为两部分:
1.给pointcut.pointcutExpression赋值
2.targerClass匹配pointcut检验
而第一部分的赋值在pc.getClassFilter()里,看方法代码
1 | public ClassFilter getClassFilter() { |
以上代码的核心逻辑就是给AspectJExpressionPointcut.PointcutExpression赋值,这个就是开篇时的例子代码:@Pointcut(“execution( * com.xuekg.constructor..(..))”)。赋值操作是通过PointcutParser、PatternParser和Pointcut来完成的。具体的赋值过程入口为pc = pc.resolve(resolutionScope)。resolve方法调用栈:
Pointcut.resolve()
-this.resolveBindings()
–searchType.findPointcut(name)
searchType就是TimeFeeAspect,name就是cut方法名
… 经过getPointcuts().iterator循环 …
—Java15ReflectionBasedReferenceTypeDelegate.getDeclaredPointcuts()
—-AjType.getDeclaredPointcuts()
—–Method[] methods = clazz.getDeclaredMethods()
clazz就是TimeFeeAspect,循环methods,判断method是否有@Pointcut注解:Pointcut pcAnn = method.getAnnotation(Pointcut.class);
拿到pcAnn.value()。构造成一个Pointcut,再构造成一个PointcutExpressionImpl,再构造成一个PointcutImpl。最后AspectJExpressionPointcut.PointcutExpression被赋值了。切入点的表达式已经有了,接下来要做的自然是使用表达式来验证目标了。这部分即是AopUtils.canApply()方法的第二部分的逻辑。
下面看AopUtils.canApply()方法的第二部分:targerClass匹配pointcut检验,首先看pc.getClassFilter().matches(targetClass)如果匹配,代码往下走;如果不匹配,直接返回false。接着走(2)处代码,即获取targerClass的所有接口和父类,遍历每个类的每个方法,使用Pointcut的expression去match每个方法,一旦有匹配上,返回true,退出遍历。所以匹配的逻辑就是重点了。这里列下关键的代码段
1 | // 可以看到这个方法中使用了双重检查机制(double check),double check结合synchronized可以有效的 |
matchesExactlyByName(..)比较全限定类名是否匹配。会把pointcut的expression和目标类的全限定名变为.分隔的数组,对应下标值依次比较。像这样:pattern[pi].matches(target[ti])。
matchesParameters(..)比较方法的参数是否匹配
matchesBounds(..)比较方法或参数带?时是否匹配
annotationPattern(..)比较方法或参数的注解是否匹配
当然,根据&&短路原则,前面的方法返回false时,后面的方法不用走了,直接返回结果。本例debug时,pointcut为”execution( com.xuekg.constructor..(..)),目标target类为:别放一起,两者比较,包名不一样,所以不匹配,返回false
03 CglibAopProxy或JdkDynamicAopProxy生成Proxy代理类阶段
我们知道AbstractAutoProxyCreator类的postProcessBeforeInstantiation()和postProcessAfterInitialization()方法都可以生成proxy代理类。都是调用的this.createProxy(..)`方法,这就是生成Proxy代理类入口,代码如下
1 | protected Object createProxy(Class<?> beanClass, String beanName, |
首先创建了一个ProxyFactory,看名字就知道含义:proxy的工厂。注意这句代码proxyFactory.copyFrom(this),其中的this为AnnotationAwareAspectJAutoProxyCreator对象,还记得吗,spring项目初始化时,aop相关实例化xxxProxyAutoConfiguration就是AnnotationAwareAspectJAutoProxyCreator类,他的proxyTargetClass和exposeProxy等属性通过proxyFactory.copyFrom(this)传递给proxyFactory属性。
然后通过buildAdvisors(beanName, specificInterceptors)方法构建advisor,看他的specificInterceptors参数,往上追溯可以知道是post-process方法传递进来的,我们可以知道specificInterceptors之一就是InstantiationModelAwarePointcutAdvisorImpl实例。所以生成的advisors包含InstantiationModelAwarePointcutAdvisorImpl实例,同时将advisors赋值给proxyFactory.addAdvisors(advisors)。接下来,通过proxyFactory.getProxy(getProxyClassLoader())生成proxy代理类
1 | public Object getProxy( ClassLoader classLoader){ |
先说createAopProxy(),看内部实现
1 | protected final synchronized AopProxy createAopProxy() { |
createAopProxy方法正式创建xxxAopProxy了,到底是创建Jdk动态代理还是cglib动态代理呢,看条件,条件之一proxyTargetClass的值是我们所能控制的,因为可以在我们的项目启动类中加入类似
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)的配置。这是全局设置。还有每次创建proxy的设置:shouldProxyTargetClass(beanClass, beanName),逻辑为beanName有没有接口,有接口proxyTargetClass=false;没有接口,proxyTargetClass=true。如果有Controller的话,由于没有接口,所以生成代理时是cglib代理,即new ObjenesisCglibAopProxy(config),看其及父类构造函数
1 | public CglibAopProxy(AdvisedSupport config) throws AopConfigException { |
AdvisedSupport是proxyFactory的父类,CglibAopProxy拥有一个AdvisedSupport类型的advised属性,而这个advised拥有advisors、proxyTargetClass、exposeProxy等,所以CglibAopProxy就等于有了advisors、proxyTargetClass、exposeProxy。
aopProxy已经创建了,下面说另一部分:getProxy(classLoader),看内部代码
1 | public Object getProxy( ClassLoader classLoader){ |
getProxy()方法会创建一个Enhancer对象,这个工具类负责生成代理类的class,只不过这个class不是文本的形式,而是在内存中。Enhancer`会被赋值生成proxy代理类是用于的属性,说下这些属性即作用
superclass:目标类
Interfaces:目标类和代理类的接口
namingPolicy:生成代理类名称策略, 如”xxx$$EnhancerBySpringCGLIB$$d2e4a5ae”这个文本形式就是namingPolicy生成的
strategy:生成代理类class策略
callbackFilter:设置对不同方法执行不同的回调逻辑,或者根本不执行回调
callBackTypes:都会赋值给Enhancer对象用于生成Proxy代理类。callBackTypes就是aop interceptor,CGLIB中对于方法的拦截是通过将自定义的拦截器(实现MethodInterceptor接口)加入Callback中并在调用代理时直接激活拦截器中的intercept方法来实现的,DynamicAdvisedInterceptor继承自MethodInterceptor,而包含AspectJMethodBeforeAdvice的advised属性又封装在DynamicAdvisedInterceptor中,DynamicAdvisedInterceptor又赋值到callBack。所以当我们curl访问目标类方法时会被proxy代理类的拦截器拦截,继而会走切面的AspectJMethodBeforeAdvice对象的方法执行切面逻辑。下面就详细下CallbackTypes,看getCallbacks方法代码,如下
1 | private Callback[] getCallbacks(Class<?> rootClass) throws Exception { |
可以看到,mainCallbacks是最终生成的callbacks,包含7个callback,属于几类,分别说下
DynamicAdvisedInterceptor:核心的aop interceptor,aspect的AspectJMethodBeforeAdvice就被赋值在这里。访问业务方法时就会先访问DynamicAdvisedInterceptor的intercept方法,详情看此方法
StaticUnadvisedExposedInterceptor:用于没有advice chain的静态目标
SerializableNoOp 什么操作也不做,代理类直接调用被代理的方法不进行拦截
Dispatcher 每次调用都会重新加载被代理的对象
···
下面看下getProxy方法createProxyClassAndInstance方法,这个方法创建proxy class并实例化为instance,看下内部代码
1 | protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { |
已经懵了。
下面看下调用一个方法时,如何进入到代理类的方法中去,DynamicAdvisedInterceptor.intercept()方法。我们看下这个方法的内部
1 | public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { |
方法(1)处获取MethodIntercetor集合,从而获取对应的dvice集合
方法(2)处创建一个CglibMethodInvocation,CglibMethodInvocation拥有一个MethodInterceptor集合,通过proceed()方法执行interceptor chain。所有这里运用了一个设计模式:责任链模式的变种,类似tomcat filter的形式。
具体如下,运行这个链的入口是DynamicAdvisedInterceptor.intercept()。这个方法会获取interceptor集合,然后创建一个CglibMethodInvocation对象,并把interceptor集合传递给CglibMethodInvocation对象的interceptorsAndDynamicMethodMatchers属性, 然后调用CglibMethodInvocation对象的proceed(),proceed()会遍历interceptorsAndDynamicMethodMatchers(其实就是interceptor集合)从而执行每个interceptor.invoke(MethodInvocation)自身的逻辑,因为这个时候MethodInvocation会传递给invoke方法,所以每一个invoke方法内部都会执行MethodInvocation.proceed(),从而这样形成了一个链式的调用关系。我们看下链式代码结构
1 | //MethodInvocation类 |
调用了MethodBeforeAdvice.before()方法,MethodBeforeAdvice持有aspectJAdviceMethod属性,这个aspectJAdviceMethod其实就TimeFeeAspect.doTimeFeeIntercepter()方法,所以before方法会进去我们定义的Aspect切面,从而实现了切入方法的功能