目标:
1. 什么是AOP,什么是AspectJ
2. 什么是Spring AOP
3. Spring AOP注解版实现原理
4. Spring AOP切面原理解析
一. 认识AOP及其使用@H_404_14@
详见博文1: 5.1 Spring5源码--Spring AOP源码分析一
二. AOP的特点@H_404_14@
2.1 Spring AOP
2.1.1 他是基于动态代理实现的
Spring 提供了很多的实现AOP的方式:Spring 接口方式,schema配置方式和注解的方式.
如果使用接口方式引入AOP,就是用JDK提供的动态代理来实现.
如果没有使用接口的方式引入. 那么就是使用CGLIB来实现的.
Spring 提供了很多的实现AOP的方式:Spring 接口方式,schema配置方式和注解的方式.
如果使用接口方式引入AOP,就是用JDK提供的动态代理来实现.
如果没有使用接口的方式引入. 那么就是使用CGLIB来实现的.
Spring使用接口方式实现AOP,下面有详细说明.
研究使用接口方式实现AOP,目的是为了更好地理解spring使用动态代理实现AOP的两种方式
2.1.2 spring3.2以后,spring-core直接把CGLIB和ASM的源码引入进来了,所以,后面我们就不需要再显示的引入这两个依赖了.
2.1.3 Spring AOP依赖于Spring ioc容器来管理
2.1.4 Spring AOP只能作用于bean的方法.
如果某个类,没有注入到ioc容器中,那么是不能被增强的
2.1.5 Spring提供了对AspectJ的支持,但只提供了部分功能的支持: 即AspectJ的切点解析(表达式)和匹配
我们在写切面的时候,经常使用到的@Aspect,@Before, @Pointcut, @After, @AfterReturning, @AfterThrowing等就是AspectJ提供的.
我们知道AspectJ很好用,效率也很高. 那么为什么Spring不使用AspectJ全套的东西呢? 尤其是AspectJ的静态织入.
先来看看AspectJ有哪些特点
AspectJ的特点 1. AspectJ属于静态织入. 他是通过修改代码实现的. 它的织入时机有三种 1) Compile-time weaving: 编译期织入. 例如: 类A使用AspectJ增加了一个属性. 类B引用了类A,这个场景就需要在编译期的时候进行织入,否则类B就没有办法编译,会报错. 2) Post-compile weaving: 编译后织入.也就是已经生成了.class文件了,或者是都已经达成jar包了. 这个时候,如果我们需要增强,就要使用到编译后织入 3) Loading-time weaving: 指的是在加载类的时候进行织入. 2. AspectJ实现了对AOP变成完全的解决方案. 他提供了很多Spring AOP所不能实现的功能 3. 由于AspectJ是在实际代码运行前就完成了织入,因此可以认为他生成的类是没有额外运行开销的.
扩展: 这里为什么没有使用到AspectJ的静态织入呢? 因为如果引入静态织入,需要使用AspectJ自己的解析器. AspectJ文件是以aj后缀结尾的文件,这个文件Spring是没有办法,因此要使用AspectJ自己的解析器进行解析. 这样就增加了Spring的成本.
2.1.6 Spring AOP和AspectJ的比较。由于,Spring AOP基于代理实现. 容器启动时会生成代理对象,方法调用时会增加栈的深度。使得Spring AOP的性能不如AspectJ好。
三. AOP的配置方式@H_404_14@
上面说了Spring AOP和AspectJ. 也说道了AspectJ定义了很多注解,比如: @Aspect,@Pointcut,@After等等. 但是,我们使用Spring AOP是使用纯java代码写的. 也就是说他完全属于Spring,和AspectJ没有什么关系. Spring只是沿用了AspectJ中的概念. 包括AspectJ提供的jar报的注解. 但是,并不依赖于AspectJ的功能.
我们使用的@Aspect,@After等注解都是来自于AspectJ,但是其功能的实现是纯Spring AOP自己实现的.
Spring AOP有三种配置方式.
-
第一种: 基于接口方式的配置. 在Spring1.2版本,提供的是完全基于接口方式实现的
-
第二种: 基于schema-based配置. 在spring2.0以后使用了xml的方式来配置.
-
第三种: 基于注解@Aspect的方式. 这种方式是最简单,方便的. 这里虽然叫做AspectJ,但实际上和AspectJ一点关系也没有.
因为我们在平时工作中主要使用的是注解的方式配置AOP,而注解的方式主要是基于第一种接口的方式实现的. 所以,我们会重点研究第一种和第三种配置方式.
3.1 基于接口方式的配置. 在Spring1.2版本,提供的是完全基于接口方式实现的
这种方式是最古老的方式,但由于spring做了很好的向后兼容,现在还是会有很多代码使用这种方式, 比如:声明式事务.
我们要了解这种配置方式还有另一个原因,就是我们要看源码. 源码里对接口方式的配置进行了兼容处理. 同时,1)">看源码的入口是从接口方式的配置开始的.
那么,在没有引入AspectJ的时候,Spring是如何实现AOP的呢? 我们来看一个例子:
1. 定义一个业务逻辑接口类
package com.lxl.www.aop.interfaceAop; /** * 使用接口方式实现AOP,默认通过JDK的动态代理来实现. 非接口方式,使用的是cglib实现动态代理 * * 业务接口类-- 计算器接口类 * * 定义三个业务逻辑方法 */ public interface IBaseCalculate { int add(int numA,int numB); int sub(int div(int multi(int mod( numB); }
2.定义业务逻辑类
package com.lxl.www.aop.interfaceAop;//业务类,也是目标对象 import com.lxl.www.aop.Calculate; import org.springframework.aop.framework.AopContext; import org.springframework.stereotype.Service; * * 业务实现类 -- 基础计算器 */ class BaseCalculate implements IBaseCalculate { @Override numB) { System.out.println("执行目标方法: add"); return numA + numB; } @Override 执行目标方法: subreturn numA -执行目标方法: multireturn numA *执行目标方法: divreturn numA /执行目标方法: mod); int retVal = ((Calculate) AopContext.currentProxy()).add(numA,numB); return retVal % numA; } }
3. 定义通知类
前置通知
package com.lxl.www.aop.interfaceAop; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.stereotype.Component; import java.lang.reflect.Method; * * 定义前置通知 * 实现MethodBeforeAdvice接口 BaseBeforeAdvice implements MethodBeforeAdvice { * * * @param method 切入的方法 * @param args 切入方法的参数 * @param target 目标对象 * @throws Throwable */ @Override void before(Method method,Object[] args,Object target) throws Throwable { System.===========进入beforeAdvice()============); System.前置通知--即将进入切入点方法===========进入beforeAdvice() 结束============\n); } }
后置通知
package com.lxl.www.aop.interfaceAop; import org.aspectj.lang.annotation.AfterReturning; import org.springframework.aop.AfterAdvice; import org.springframework.aop.AfterReturningAdvice; import org.springframework.stereotype.Component; import java.lang.reflect.Method; * * 后置通知 * 实现AfterReturningAdvice接口 BaseAfterReturnAdvice implements AfterReturningAdvice { * * * @param returnValue 切入点执行完方法的返回值,但不能修改 * @param method 切入点方法 * @param args 切入点方法的参数数组 * @param target 目标对象 * @throws Throwable afterReturning(Object returnValue,Method method,1)">\n==========进入afterReturning()===========后置通知--切入点方法执行完成==========进入afterReturning() 结束=========== ); } }
环绕通知
package com.lxl.www.aop.interfaceAop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.stereotype.Component; import java.lang.reflect.Method; * * 环绕通知 * 实现MethodInterceptor接口 BaseAroundAdvice implements MethodInterceptor { * * invocation :连接点 public Object invoke(MethodInvocation invocation) throws Throwable { System.===========around环绕通知方法 开始=========== 调用目标方法之前执行的动作 System.环绕通知--调用方法之前: 执行 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行 Object returnValue = invocation.proceed(); System.环绕通知--调用方法之后: 执行===========around环绕通知方法 结束===========return returnValue; } }
配置类
package com.lxl.www.aop.interfaceAop; import org.springframework.aop.framework.Proxyfactorybean; import org.springframework.context.annotation.Bean; * * 配置类 MainConfig { * * 被代理的对象 * @return @Bean IBaseCalculate baseCalculate() { return new BaseCalculate(); } * * 前置通知 * @return BaseBeforeAdvice baseBeforeAdvice() { BaseBeforeAdvice(); } * * 后置通知 * @return BaseAfterReturnAdvice baseAfterReturnAdvice() { BaseAfterReturnAdvice(); } * * 环绕通知 * @return BaseAroundAdvice baseAroundAdvice() { BaseAroundAdvice(); } * * 使用接口方式,一次只能给一个类增强,如果想给多个类增强,需要定义多个Proxyfactorybean * 而且,曾增强类的粒度是到类级别的. 不能指定对某一个方法增强 * @return Proxyfactorybean calculateProxy() { Proxyfactorybean proxyfactorybean = Proxyfactorybean(); proxyfactorybean.setInterceptorNames(baseAfterReturnAdvice",baseBeforeAdvicebaseAroundAdvice); proxyfactorybean.setTarget(baseCalculate()); proxyfactorybean; } }
之前说过,AOP是依赖ioc的,必须将其注册为bean才能实现AOP功能
方法入口
package com.lxl.www.aop.interfaceAop; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; InterfaceMainClass{ static main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.); IBaseCalculate calculate = context.getBean(calculateProxyout.println(calculate.getClass()); calculate.add(1,1)">3); } }
执行结果:
===========进入beforeAdvice()============ 前置通知--即将进入切入点方法 ===========进入beforeAdvice() 结束============ ===========around环绕通知方法 开始=========== 环绕通知--调用方法之前: 执行 执行目标方法: add 环绕通知--调用方法之后: 执行 ===========around环绕通知方法 结束=========== ==========进入afterReturning()=========== 后置通知--切入点方法执行完成 ==========进入afterReturning() 结束===========
通过观察,我们发现,执行的顺序是: 前置通知-->环绕通知的前置方法 --> 目标逻辑 --> 环绕通知的后置方法 --> 后置通知.
那么到底是先执行前置通知,还是先执行环绕通知的前置方法呢? 这取决于配置文件的配置顺序
这里,我们将环绕通知放在最后面,环绕通知在前置通知之后执行.
@Bean public Proxyfactorybean calculateProxy() { Proxyfactorybean proxyfactorybean = new Proxyfactorybean(); proxyfactorybean.setInterceptorNames( "baseAfterReturnAdvice","baseBeforeAdvice","baseAroundAdvice"); proxyfactorybean.setTarget(baseCalculate()); return proxyfactorybean; }
那么,如果我们将环绕通知放在前置通知之前. 就会先执行环绕通知
@Bean public Proxyfactorybean calculateProxy() { Proxyfactorybean proxyfactorybean = new Proxyfactorybean(); proxyfactorybean.setInterceptorNames("baseAroundAdvice","baseAfterReturnAdvice","baseBeforeAdvice"); proxyfactorybean.setTarget(baseCalculate()); return proxyfactorybean; }
运行结果
===========around环绕通知方法 开始=========== 环绕通知--调用方法之前: 执行 ===========进入beforeAdvice()============ 前置通知--即将进入切入点方法 ===========进入beforeAdvice() 结束============ 执行目标方法: add ==========进入afterReturning()=========== 后置通知--切入点方法执行完成 ==========进入afterReturning() 结束=========== 环绕通知--调用方法之后: 执行 ===========around环绕通知方法 结束===========
思考: 使用Proxyfactorybean实现AOP的方式有什么问题?
1. 通知加在类级别上,而不是方法上. 一旦使用这种方式,那么所有类都会被织入前置通知,后置通知,环绕通知. 可有时候我们可能并不想这么做
2. 每次只能指定一个类. 也就是类A要实现加日志,那么创建一个A的Proxyfactorybean,类B也要实现同样逻辑的加日志. 但是需要再写一个Proxyfactorybean.
基于以上两点原因. 我们需要对其进行改善.
下面,我们来看看,Proxyfactorybean是如何实现动态代理的?
Proxyfactorybean是一个工厂bean,我们知道工厂bean在创建类的时候调用的是getObject(). 下面看一下源码
Proxyfactorybean extends ProxyCreatorSupport implements factorybean<Object>,BeanClassLoaderAware,beanfactoryAware { ...... @Override @Nullable Object getObject() throws BeansException { * * 初始化通知链: 将通知放入链中 * 后面初始化的时候,是通过责任链的方式调用这些通知链的的. * 那么什么是责任链呢? */ initializeAdvisorChain(); if (isSingleton()) { * * 创建动态代理 */ getSingletonInstance(); } else { if (this.targetName == null) { logger.info(Using non-singleton proxies with singleton targets is often undesirable. " + Enable prototype proxies by setting the 'targetName' property.); } newPrototypeInstance(); } } ...... }
发送到initializeAdvisorChain是初始化各类型的Advisor通知,比如,我们上面定义的通知有三类: "baseAroundAdvice","baseBeforeAdvice". 这里采用的是责任链调用的方式.
然后调用getSingletonInstance()创建动态代理.
private synchronized Object getSingletonInstance() { this.singletonInstance == ) { this.targetSource = freshTargetSource(); this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { Rely on AOP infrastructure to tell us what interfaces to proxy. Class<?> targetClass = getTargetClass(); if (targetClass == ) { throw new factorybeanNotInitializedException(Cannot determine target class for proxy); } setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass,this.proxyClassLoader)); } Initialize the shared singleton instance. super.setFrozen(.freezeProxy); * * 创建动态代理 this.singletonInstance = getProxy(createAopProxy()); } .singletonInstance; }
调用getProxy(CreateAopProxy())调用代理创建动态代理. 我们这里使用接口的方式,这里调用的是JDKDynamicAopProxy动态代理.
@Override Object getProxy(@Nullable ClassLoader classLoader) { (logger.isTraceEnabled()) { logger.trace(Creating JDK dynamic proxy: " + .advised.getTargetSource()); } Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised,1)">true); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); * * 创建动态代理 */ return Proxy.newProxyInstance(classLoader,proxiedInterfaces,this); }
最终,动态代理创建,就是在JDKDynamicAopProxy了. 通过执行Proxy.newProxyInstance(classLoader,this);创建动态代理实例.
其实我们通过ctx.getBean("calculateProxy")获得的类,就是通过JDKDynamicAopProxy创建的动态代理类.
这里也看出,为什么每次只能给一个类创建动态代理了.
上面提到了责任链,那么什么是责任链呢? 如下图所示:
有一条流水线. 比如生产流水线. 里面有许多道工序. 完成工序1,才能进行工序2,依此类推.
流水线上的工人也是各司其职. 工人1做工序1,工人2做工序2,工人3做工序3.....这就是一个简单的流水线模型.
工人的责任就是完成每一道工序,那么所有工人的责任就是完成这条流水线. 这就是工人的责任链.
其实,我们的通知也是一类责任链. 比如,前置通知,环绕通知,异常通知. 他们的执行都是有顺序的. 一个工序完成,做另一个工序.各司其职. 这就是责任链.
为了能工统一调度,我们需要保证,所有工人使用的都是同一个抽象. 这样,就可以通过抽象类的调用方式. 各个工人有具体的工作实现.
通知也是如此. 需要有一个抽象的通知类Advicor. 进行统一调用.
结合上面的demo,来看一个责任链调用的demo.
上面我们定义了两个方法. 一个是前置通知BaseBeforeAdvice 实现了MethodBeforeAdvice,另一个是环绕通知BaseAroundAdvice 实现了MethodInterceptor. 如果想把这两个通知放在一个链上. 那么他们必须实现相同的接口. 但是,现在不同.
我们知道环绕通知,由两部分,一部分是环绕通知的前置通知,一部分是环绕通知的后置通知. 所以,我们可以将前置通知看作是环绕通知的前置通知部分.
package com.lxl.www.aop.interfaceAop.chainDemo; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.MethodBeforeAdvice; * * 为什么 我们可以让MethodBeforeAdvice 前置通知继承自环绕通知的接口呢? * 主要原因是,环绕通知的前半部分,就是前置通知 BeforeAdviceInterceptor implements MethodInterceptor { 前置通知 MethodBeforeAdvice methodBeforeAdvice; BeforeAdviceInterceptor(MethodBeforeAdvice methodBeforeAdvice) { this.methodBeforeAdvice = methodBeforeAdvice; } * * 使用了环绕通知的前半部分. 就是一个前置通知 * @param invocation the method invocation joinpoint * @return * @throws Throwable @Override Object invoke(MethodInvocation invocation) throws Throwable { methodBeforeAdvice.before(invocation.getMethod(),invocation.getArguments(),invocation.getClass()); return invocation.proceed(); } }
这段代码包装了前置通知,让其扩展为实现MethodInterceptor接口. 这是一个扩展接口的方法.
接下来我们要创建一条链. 这条链就可以理解为流水线上各个工人. 每个工人处理一个工序. 为了能够统一调用. 所有的工人都要实现同一个接口. 责任链的定义如下:
/**
* 把一条链上的都初始化
*
* 有一条链,这条链上都有一个父类接口 MethodInterceptor.
* 也就是说,链上都已一种类型的工人. 但每种工人的具体实现是不同的. 不同的工人做不同的工作
*
* 这里定义了一个责任链. 连上有两个工人. 一个是前置通知. 一个是环绕通知.
* 前置通知和环绕通知实现的接口是不同的. 为了让他们能够在一条链上工作. 我们自定义了一个MethodBeforeAdviceInterceptor
* 相当于为BaseBeforeAdvice()包装了一层MethodInterceptor
*/
List<MethodInterceptor> list = new ArrayList<>();
list.add(new BeforeAdviceInterceptor(new BaseBeforeAdvice()));
list.add(new BaseAroundAdvice());
这里定义了一个责任链. 连上有两个工人. 一个是前置通知. 一个是环绕通知.
前置通知和环绕通知实现的接口是不同的. 为了让他们能够在一条链上工作. 我们自定义了一个MethodBeforeAdviceInterceptor
相当于为BaseBeforAdvice()包装了一层MethodInterceptor
接下来是责任链的调用.
/** * 责任链调用 */ public static class MyMethodInvocation implements MethodInvocation { // 这是责任链 protected List<MethodInterceptor> list; // 目标类 protected final BaseCalculate target; public MyMethodInvocation(List<MethodInterceptor> list) { this.list = list; this.target = new BaseCalculate(); } int i = 0; public Object proceed() throws Throwable { if (i == list.size()) { /** * 执行到责任链的最后一环,执行目标方法 */ return target.add(2,2); } MethodInterceptor interceptor = list.get(i); i++; /** * 执行责任链调用 * 这个调用链第一环是: 包装后的前置通知 * 调用链的第二环是: 环绕通知. * 都执行完以后,执行目标方法. */ return interceptor.invoke(this); } @Override public Object getThis() { return target; } @Override public AccessibleObject getStaticPart() { return null; } @Override public Method getMethod() { try { return target.getClass().getMethod("add",int.class,int.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null; } @Override public Object[] getArguments() { return new Object[0]; } } }
这里重点看proceed() 方法. 我们循环获取了list责任链的通知,然后执行invoke()方法
proceed() 方法是一个链式循环. 刚开始i=0,list(0)是前置通知,当调用到前置通知的时候,BeforeAdviceInterceptor.invoke()方法,又调用了invocation.proceed()方法,回到了MyMethodInvocation.proceed()方法.
然后i=1,list(1)是环绕通知,当调用环绕通知的时候,又调用了invocation.proceed(); 有回到了MyMethodInvocation.proceed()方法.
这是已经是list的最后一环了,后面不会在调用invoke()方法了. 而是执行目标方法. 执行结束以后,整个调用结束.
这就是一个调用链.
对于责任链有两点:
1. 要有一个统一的调用,也就是一个共同的抽象类.
2. 使用循环或者递归,完成责任链的调用
总结:
上面这种方式,使用的是Proxyfactorybean 代理bean工厂的方式. 他有两个限制:
proxyfactorybean;
}
1. 一次只能给1个类增强,如果给多个类增强就需要定义多个Proxyfactorybean
2. 增强的粒度只能到类级别上,不能指定给某个方法增强.
这样还是有一定的限制.
为了解决能够在类级别上进行增强,Spring引入了Advisor和Pointcut.
Advisor的种类有很多
RegexpMethodPointcutAdvisor 按正则匹配类 NameMatchMethodPointcutAdvisor 按方法名匹配 DefaultbeanfactoryPointcutAdvisor xml解析的Advisor InstantiationModelAwarePointcutAdvisorImpl 注解解析的advisor(@Before @After....)
我们使用按方法名的粒度来增强,所示使用的是NameMatchMethodPointcutAdvisor
* * Advisor 种类很多: * RegexpMethodPointcutAdvisor 按正则匹配类 * NameMatchMethodPointcutAdvisor 按方法名匹配 * DefaultbeanfactoryPointcutAdvisor xml解析的Advisor * InstantiationModelAwarePointcutAdvisorImpl 注解解析的advisor(@Before @After....) @Bean NameMatchMethodPointcutAdvisor aspectAdvisor() { * * 通知和通知者的区别: * 通知(Advice) :是我们的通知类 没有带切点 * 通知者(Advisor):是经过包装后的细粒度控制方式。 带了切点 NameMatchMethodPointcutAdvisor advisor = NameMatchMethodPointcutAdvisor(); // 切入点增强的通知类型--前置通知 advisor.setAdvice(baseBeforeAdvice()); // 指定切入点方法名--div advisor.setMappedNames("div"); advisor; }
这样就可以对类中的某个方法进行增强了. 我们依然需要使用Proxyfactorybean()代理工厂类来进行增强
@Bean public Proxyfactorybean calculateProxy() { Proxyfactorybean userService = new Proxyfactorybean(); userService.setInterceptorNames("aspectAdvisor"); userService.setTarget(baseCalculate()); return userService; }
注意,这里增强的拦截器名称要写刚刚定义的 NameMatchMethodPointcutAdvisor 类型的拦截器.目标类还是我们的基础业务类baseCalculate()
这只是解决了可以对指定方法进行增强. 那么,如何能够一次对多个类增强呢? Spring又引入了ProxyCreator.
* 通知和通知者的区别: * 通知(Advice) :是我们的通知类 没有带切点 * 通知者(Advisor):是经过包装后的细粒度控制方式。 带了切点 NameMatchMethodPointcutAdvisor(); 切入点增强的通知类型--前置通知 advisor.setAdvice(baseBeforeAdvice()); 指定切入点方法名--div advisor.setMappedNames(div); advisor; } * * autoProxy: BeanPostProcessor 手动指定Advice方式,* @return */ BeanNameAutoProxyCreator autoProxyCreator() { 使用bean名字进行匹配 BeanNameAutoProxyCreator beanNameAutoProxyCreator = BeanNameAutoProxyCreator(); beanNameAutoProxyCreator.setBeanNames(base* 设置拦截链的名字 beanNameAutoProxyCreator.setInterceptorNames(aspectAdvisor beanNameAutoProxyCreator; }
如上代码, beanNameAutoProxyCreator.setBeanNames("base*"); 表示按照名字匹配以base开头的类,对其使用的拦截器的名称是aspectAdvisor
而aspectAdvisor使用的是按照方法的细粒度进行增强. 这样就实现了,对以base开头的类,对其中的某一个或某几个方法进行增强. 使用的增强类是前置通知.
); IBaseCalculate calculate = context.getBean(baseCalculate); calculate.add(******************); calculate.div(3); } }
这里面执行了两个方法,一个是add(),另一个是div(). 看运行结果
执行目标方法: add ****************** ===========进入beforeAdvice()============ 前置通知--即将进入切入点方法 ===========进入beforeAdvice() 结束============ 执行目标方法: div
我们看到,add方法没有被增强,而div方法被增强了,增加了前置通知.
以上就是使用接口方式实现AOP. 到最后增加了proxyCreator,能够根据正则表达式匹配相关的类,还能够为某一个指定的方法增强. 这其实就是我们现在使用的注解类型AOP的原型了.
3.2 基于注解@Aspect的方式. 这种方式是最简单,但实际上和AspectJ一点关系也没有.
3.2.1 @Aspect切面的解析原理
上面第一种方式详细研究了接口方式AOP的实现原理. 注解方式的AOP,最后就是将@Aspect 切面类中的@Befor,@After等注解解析成Advisor. 带有@Before类会被解析成一个Advisor,带有@After方法的类也会被解析成一个Advisor.....其他通知的方法也会被解析成Advisor 在Advisor中定义了增强的逻辑,也就是@Befor和@After等的逻辑,以及需要增强的方法,比如div方法.
下面来分析一下使用注解@Aspect,@After的实现原理. 上面已经说了,就是将@Before,@After生成Advisor
这里一共有三个部分.
- 第一部分: 解析@Aspect下带有@Before等的通知方法,将其解析为Advisor
- 第二部分: 在createBean的时候,创建动态代理
- 第三部分: 调用. 调用的时候,执行责任链,循环里面所有的通知. 最后输出结果.
下面我们按照这三个部分来分析.
第一步: 解析@Aspect下带有@Before等的通知方法,将其解析为Advisor. 如下图:
第一步是在什么时候执行的呢?
在createBean的时候,会调用很多PostProcessor后置处理器,在调用第一个后置处理器的时候执行.
执行的流程大致是: 拿到所有的BeanDefinition,判断类上是不是带有@Aspect注解. 然后去带有@Aspect注解的方法中找@Before,@After,@AfterReturning,@AfterThrowing,每一个通知都会生成一个Advisor
Advisor包含了增强逻辑,定义了需要增强的方法. 只不过这里是通过AspectJ的execution的方式进行匹配的.
第二步: 在createBean的时候,创建动态代理
createBean一共有三个阶段,具体在哪一个阶段创建的动态代理呢?
其实,是在最后一个阶段初始化之后,调用了一个PostProcessor后置处理器,在这里生成的动态代理
整体流程是:
在createBean的时候,在初始化完成以后调用bean的后置处理器. 拿到所有的Advisor,循环遍历Advisor,然后根据execution中的表达式进行matchs匹配. 和当前创建的这个bean进行匹配,匹配上了,就创建动态代理.
pointcut的种类有很多. 上面代码提到过的有:
/** * Advisor 种类很多: * RegexpMethodPointcutAdvisor 按正则表达式的方式匹配类 * NameMatchMethodPointcutAdvisor 按方法名匹配 * DefaultbeanfactoryPointcutAdvisor xml解析的Advisor * InstantiationModelAwarePointcutAdvisorImpl 注解解析的advisor(@Before @After....) */
第三步: 调用. 调用的时候,循环里面所有的通知. 最后输出结果.
调用的类,如果已经生成了动态代理. 那么调用的方法,就是动态代理生成的方法了.然后拿到所有的advisor,作为一个责任链调用. 执行各类通知,最后返回执行结果