目标:
1. 什么是AOP,什么是AspectJ
2. 什么是Spring AOP
3. Spring AOP注解版实现原理
4. Spring AOP切面原理解析
一. 认识AOP及其使用
详见博文1: 5.1 Spring5源码--Spring AOP源码分析一
二. AOP的特点
2.1 Spring AOP
2.1.1 他是基于动态代理实现的
- 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的配置方式
上面说了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方法没有被增强,而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,作为一个责任链调用. 执行各类通知,最后返回执行结果