5.2 spring5源码--spring AOP源码分析二--切面的配置方式 5.1 Spring5源码--Spring AOP源码分析一

前端之家收集整理的这篇文章主要介绍了5.2 spring5源码--spring AOP源码分析二--切面的配置方式 5.1 Spring5源码--Spring AOP源码分析一前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

目标:

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 他是基于动态代理实现的

  1. Spring 提供了很多的实现AOP的方式:Spring 接口方式schema配置方式注解的方式.
  2. 如果使用接口方式引入AOP,就是用JDK提供的动态代理来实现.
  3. 如果没有使用接口的方式引入. 那么就是使用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有哪些特点

  1. AspectJ的特点
  2. 1. AspectJ属于静态织入. 他是通过修改代码实现的. 它的织入时机有三种
  3. 1) Compile-time weaving: 编译期织入. 例如: A使用AspectJ增加了一个属性. B引用了类A,这个场景就需要在编译期的时候进行织入,否则类B就没有办法编译,会报错.
  4. 2) Post-compile weaving: 编译后织入.也就是已经生成了.class文件了,或者是都已经达成jar包了. 这个时候,如果我们需要增强,就要使用到编译后织入
  5. 3) Loading-time weaving: 指的是在加载类的时候进行织入.
  6.  
  7. 2. AspectJ实现了对AOP变成完全的解决方案. 他提供了很多Spring AOP所不能实现的功能
  8. 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. 定义一个业务逻辑接口类

  1. package com.lxl.www.aop.interfaceAop;
  2. /**
  3. * 使用接口方式实现AOP,默认通过JDK的动态代理来实现. 非接口方式,使用的是cglib实现动态代理
  4. *
  5. * 业务接口类-- 计算器接口类
  6. *
  7. * 定义三个业务逻辑方法
  8. */
  9. public interface IBaseCalculate {
  10. int add(int numA,int numB);
  11. int sub(int div(int multi(int mod( numB);
  12. }

 

  2.定义业务逻辑类

  1. package com.lxl.www.aop.interfaceAop;//业务类,也是目标对象
  2. import com.lxl.www.aop.Calculate;
  3. import org.springframework.aop.framework.AopContext;
  4. import org.springframework.stereotype.Service;
  5. *
  6. * 业务实现类 -- 基础计算器
  7. */
  8.  
  9. class BaseCalculate implements IBaseCalculate {
  10. @Override
  11. numB) {
  12. System.out.println("执行目标方法: add");
  13. return numA + numB;
  14. }
  15. @Override
  16. 执行目标方法: subreturn numA -执行目标方法: multireturn numA *执行目标方法: divreturn numA /执行目标方法: mod);
  17. int retVal = ((Calculate) AopContext.currentProxy()).add(numA,numB);
  18. return retVal % numA;
  19. }
  20. }

 

  3. 定义通知

  前置通知

  1. package com.lxl.www.aop.interfaceAop;
  2. import org.springframework.aop.MethodBeforeAdvice;
  3. import org.springframework.stereotype.Component;
  4. import java.lang.reflect.Method;
  5. *
  6. * 定义前置通知
  7. * 实现MethodBeforeAdvice接口
  8. BaseBeforeAdvice implements MethodBeforeAdvice {
  9. *
  10. *
  11. * @param method 切入的方法
  12. * @param args 切入方法的参数
  13. * @param target 目标对象
  14. * @throws Throwable
  15. */
  16. @Override
  17. void before(Method method,Object[] args,Object target) throws Throwable {
  18. System.===========进入beforeAdvice()============);
  19. System.前置通知--即将进入切入点方法===========进入beforeAdvice() 结束============\n);
  20. }
  21. }

 

  后置通知

  1. package com.lxl.www.aop.interfaceAop;
  2. import org.aspectj.lang.annotation.AfterReturning;
  3. import org.springframework.aop.AfterAdvice;
  4. import org.springframework.aop.AfterReturningAdvice;
  5. import org.springframework.stereotype.Component;
  6. import java.lang.reflect.Method;
  7. *
  8. * 后置通知
  9. * 实现AfterReturningAdvice接口
  10. BaseAfterReturnAdvice implements AfterReturningAdvice {
  11. *
  12. *
  13. * @param returnValue 切入点执行完方法的返回值,但不能修改
  14. * @param method 切入点方法
  15. * @param args 切入点方法的参数数组
  16. * @param target 目标对象
  17. * @throws Throwable
  18. afterReturning(Object returnValue,Method method,1)">\n==========进入afterReturning()===========后置通知--切入点方法执行完成==========进入afterReturning() 结束=========== );
  19. }
  20. }

 

  环绕通知

  1. package com.lxl.www.aop.interfaceAop;
  2. import org.aopalliance.intercept.MethodInterceptor;
  3. import org.aopalliance.intercept.MethodInvocation;
  4. import org.springframework.stereotype.Component;
  5. import java.lang.reflect.Method;
  6. *
  7. * 环绕通知
  8. * 实现MethodInterceptor接口
  9. BaseAroundAdvice implements MethodInterceptor {
  10. *
  11. * invocation :连接点
  12. public Object invoke(MethodInvocation invocation) throws Throwable {
  13. System.===========around环绕通知方法 开始=========== 调用目标方法之前执行的动作
  14. System.环绕通知--调用方法之前: 执行 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
  15. Object returnValue = invocation.proceed();
  16. System.环绕通知--调用方法之后: 执行===========around环绕通知方法 结束===========return returnValue;
  17. }
  18. }

  配置类

  1. package com.lxl.www.aop.interfaceAop;
  2. import org.springframework.aop.framework.Proxyfactorybean;
  3. import org.springframework.context.annotation.Bean;
  4. *
  5. * 配置类
  6. MainConfig {
  7. *
  8. * 被代理的对象
  9. * @return
  10. @Bean
  11. IBaseCalculate baseCalculate() {
  12. return new BaseCalculate();
  13. }
  14. *
  15. * 前置通知
  16. * @return
  17. BaseBeforeAdvice baseBeforeAdvice() {
  18. BaseBeforeAdvice();
  19. }
  20. *
  21. * 后置通知
  22. * @return
  23. BaseAfterReturnAdvice baseAfterReturnAdvice() {
  24. BaseAfterReturnAdvice();
  25. }
  26. *
  27. * 环绕通知
  28. * @return
  29. BaseAroundAdvice baseAroundAdvice() {
  30. BaseAroundAdvice();
  31. }
  32. *
  33. * 使用接口方式,一次只能给一个类增强,如果想给多个类增强,需要定义多个Proxyfactorybean
  34. * 而且,曾增强类的粒度是到类级别的. 不能指定对某一个方法增强
  35. * @return
  36. Proxyfactorybean calculateProxy() {
  37. Proxyfactorybean proxyfactorybean = Proxyfactorybean();
  38. proxyfactorybean.setInterceptorNames(baseAfterReturnAdvice",baseBeforeAdvicebaseAroundAdvice);
  39. proxyfactorybean.setTarget(baseCalculate());
  40. proxyfactorybean;
  41. }
  42. }

 

之前说过,AOP是依赖ioc的,必须将其注册为bean才能实现AOP功能

  方法入口

  1. package com.lxl.www.aop.interfaceAop;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  4. InterfaceMainClass{
  5. static main(String[] args) {
  6. ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.);
  7. IBaseCalculate calculate = context.getBean(calculateProxyout.println(calculate.getClass());
  8. calculate.add(1,1)">3);
  9. }
  10. }

 

  执行结果:

  1. ===========进入beforeAdvice()============
  2. 前置通知--即将进入切入点方法
  3. ===========进入beforeAdvice() 结束============
  4.  
  5. ===========around环绕通知方法 开始===========
  6. 环绕通知--调用方法之前: 执行
  7. 执行目标方法: add
  8. 环绕通知--调用方法之后: 执行
  9. ===========around环绕通知方法 结束===========
  10.  
  11. ==========进入afterReturning()===========
  12. 后置通知--切入点方法执行完成
  13. ==========进入afterReturning() 结束===========

通过观察,我们发现,执行的顺序是: 前置通知-->环绕通知的前置方法 --> 目标逻辑 --> 环绕通知的后置方法 --> 后置通知

那么到底是先执行前置通知,还是先执行环绕通知的前置方法呢? 这取决于配置文件的配置顺序

这里,我们将环绕通知放在最后面,环绕通知在前置通知后执行

  1.   @Bean
  2. public Proxyfactorybean calculateProxy() {
  3. Proxyfactorybean proxyfactorybean = new Proxyfactorybean();
  4. proxyfactorybean.setInterceptorNames( "baseAfterReturnAdvice","baseBeforeAdvice","baseAroundAdvice");
  5. proxyfactorybean.setTarget(baseCalculate());
  6. return proxyfactorybean;
  7. }

那么,如果我们将环绕通知放在前置通知之前. 就会先执行环绕通知

  1.   @Bean
  2. public Proxyfactorybean calculateProxy() {
  3. Proxyfactorybean proxyfactorybean = new Proxyfactorybean();
  4. proxyfactorybean.setInterceptorNames("baseAroundAdvice","baseAfterReturnAdvice","baseBeforeAdvice");
  5. proxyfactorybean.setTarget(baseCalculate());
  6. return proxyfactorybean;
  7. }

 

运行结果

  1. ===========around环绕通知方法 开始===========
  2. 环绕通知--调用方法之前: 执行
  3. ===========进入beforeAdvice()============
  4. 前置通知--即将进入切入点方法
  5. ===========进入beforeAdvice() 结束============
  6.  
  7. 执行目标方法: add
  8.  
  9. ==========进入afterReturning()===========
  10. 后置通知--切入点方法执行完成
  11. ==========进入afterReturning() 结束===========
  12. 环绕通知--调用方法之后: 执行
  13. ===========around环绕通知方法 结束===========

 

思考: 使用Proxyfactorybean实现AOP的方式有什么问题?

1. 通知加在类级别上,而不是方法上. 一旦使用这种方式,那么所有类都会被织入前置通知,后置通知,环绕通知. 可有时候我们可能并不想这么做

2. 每次只能指定一个类. 也就是类A要实现加日志,那么创建一个A的Proxyfactorybean,类B也要实现同样逻辑的加日志. 但是需要再写一个Proxyfactorybean

基于以上两点原因. 我们需要对其进行改善. 

 

下面,我们来看看,Proxyfactorybean是如何实现动态代理的?

Proxyfactorybean是一个工厂bean,我们知道工厂bean在创建类的时候调用的是getObject(). 下面看一下源码

  1. Proxyfactorybean extends ProxyCreatorSupport
  2. implements factorybean<Object>,BeanClassLoaderAware,beanfactoryAware {
  3. ......
  4.    @Override
  5. @Nullable
  6. Object getObject() throws BeansException {
  7. *
  8. * 初始化通知链: 通知放入链中
  9. * 后面初始化的时候,是通过责任链的方式调用这些通知链的的.
  10. * 那么什么是责任链呢?
  11. */
  12. initializeAdvisorChain();
  13. if (isSingleton()) {
  14. *
  15. * 创建动态代理
  16. */
  17. getSingletonInstance();
  18. }
  19. else {
  20. if (this.targetName == null) {
  21. logger.info(Using non-singleton proxies with singleton targets is often undesirable. " +
  22. Enable prototype proxies by setting the 'targetName' property.);
  23. }
  24. newPrototypeInstance();
  25. }
  26. }
  27. ......
  28. }

 

发送到initializeAdvisorChain是初始化各类型的Advisor通知,比如,我们上面定义的通知有三类: "baseAroundAdvice","baseBeforeAdvice". 这里采用的是责任链调用的方式. 

然后调用getSingletonInstance()创建动态代理. 

  1. private synchronized Object getSingletonInstance() {
  2. this.singletonInstance == ) {
  3. this.targetSource = freshTargetSource();
  4. this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
  5. Rely on AOP infrastructure to tell us what interfaces to proxy.
  6. Class<?> targetClass = getTargetClass();
  7. if (targetClass == ) {
  8. throw new factorybeanNotInitializedException(Cannot determine target class for proxy);
  9. }
  10. setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass,this.proxyClassLoader));
  11. }
  12. Initialize the shared singleton instance.
  13. super.setFrozen(.freezeProxy);
  14. *
  15. * 创建动态代理
  16. this.singletonInstance = getProxy(createAopProxy());
  17. }
  18. .singletonInstance;
  19. }

调用getProxy(CreateAopProxy())调用代理创建动态代理. 我们这里使用接口的方式,这里调用的是JDKDynamicAopProxy动态代理.

  1. @Override
  2. Object getProxy(@Nullable ClassLoader classLoader) {
  3. (logger.isTraceEnabled()) {
  4. logger.trace(Creating JDK dynamic proxy: " + .advised.getTargetSource());
  5. }
  6. Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised,1)">true);
  7. findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
  8. *
  9. * 创建动态代理
  10. */
  11. return Proxy.newProxyInstance(classLoader,proxiedInterfaces,this);
  12. }

 

最终,动态代理创建,就是在JDKDynamicAopProxy了.  通过执行Proxy.newProxyInstance(classLoader,this);创建动态代理实例. 

其实我们通过ctx.getBean("calculateProxy")获得的类,就是通过JDKDynamicAopProxy创建的动态代理类. 

这里也看出,为什么每次只能给一个类创建动态代理了. 

 

上面提到了责任链,那么什么是责任链呢? 如下图所示:

 

 有一条流水线. 比如生产流水线. 里面有许多道工序. 完成工序1,才能进行工序2,依此类推. 

流水线上的工人也是各司其职. 工人1做工序1,工人2做工序2,工人3做工序3.....这就是一个简单的流水线模型.

工人的责任就是完成每一道工序,那么所有工人的责任就是完成这条流水线. 这就是工人的责任链.

其实,我们的通知也是一类责任链. 比如,前置通知,环绕通知,异常通知. 他们的执行都是有顺序的. 一个工序完成,做另一个工序.各司其职. 这就是责任链.

为了能工统一调度,我们需要保证,所有工人使用的都是同一个抽象. 这样,就可以通过抽象类的调用方式. 各个工人有具体的工作实现. 

通知也是如此. 需要有一个抽象的通知类Advicor. 进行统一调用.

结合上面的demo,来看一个责任链调用的demo.

上面我们定义了两个方法. 一个是前置通知BaseBeforeAdvice 实现了MethodBeforeAdvice,另一个是环绕通知BaseAroundAdvice 实现了MethodInterceptor. 如果想把这两个通知放在一个链上. 那么他们必须实现相同的接口. 但是,现在不同. 

我们知道环绕通知,由两部分,一部分是环绕通知的前置通知,一部分是环绕通知的后置通知. 所以,我们可以将前置通知看作是环绕通知的前置通知部分.

  1. package com.lxl.www.aop.interfaceAop.chainDemo;
  2. import org.aopalliance.intercept.MethodInterceptor;
  3. import org.aopalliance.intercept.MethodInvocation;
  4. import org.springframework.aop.MethodBeforeAdvice;
  5. *
  6. * 为什么 我们可以让MethodBeforeAdvice 前置通知继承自环绕通知的接口呢?
  7. * 主要原因是,环绕通知的前半部分,就是前置通知
  8. BeforeAdviceInterceptor implements MethodInterceptor {
  9. 前置通知
  10. MethodBeforeAdvice methodBeforeAdvice;
  11.  
  12. BeforeAdviceInterceptor(MethodBeforeAdvice methodBeforeAdvice) {
  13. this.methodBeforeAdvice = methodBeforeAdvice;
  14. }
  15. *
  16. * 使用了环绕通知的前半部分. 就是一个前置通知
  17. * @param invocation the method invocation joinpoint
  18. * @return
  19. * @throws Throwable
  20. @Override
  21. Object invoke(MethodInvocation invocation) throws Throwable {
  22. methodBeforeAdvice.before(invocation.getMethod(),invocation.getArguments(),invocation.getClass());
  23. return invocation.proceed();
  24. }
  25. }

 

这段代码包装了前置通知,让其扩展为实现MethodInterceptor接口. 这是一个扩展接口的方法

接下来我们要创建一条链. 这条链就可以理解为流水线上各个工人. 每个工人处理一个工序. 为了能够统一调用. 所有的工人都要实现同一个接口. 责任链的定义如下:

  1. /**
  2. * 把一条链上的都初始化
  3. *
  4. * 有一条链,这条链上都有一个父类接口 MethodInterceptor.
  5. * 也就是说,链上都已一种类型的工人. 但每种工人的具体实现是不同的. 不同的工人做不同的工作
  6. *
  7. * 这里定义了一个责任链. 连上有两个工人. 一个是前置通知. 一个是环绕通知.
  8. * 前置通知和环绕通知实现的接口是不同的. 为了让他们能够在一条链上工作. 我们自定义了一个MethodBeforeAdviceInterceptor
  9. * 相当于为BaseBeforeAdvice()包装了一层MethodInterceptor
  10. */
  11. List<MethodInterceptor> list = new ArrayList<>();
  12. list.add(new BeforeAdviceInterceptor(new BaseBeforeAdvice()));
  13. list.add(new BaseAroundAdvice());

这里定义了一个责任链. 连上有两个工人. 一个是前置通知. 一个是环绕通知.

前置通知和环绕通知实现的接口是不同的. 为了让他们能够在一条链上工作. 我们自定义了一个MethodBeforeAdviceInterceptor

相当于为BaseBeforAdvice()包装了一层MethodInterceptor

接下来是责任链的调用

  1. /**
  2. * 责任链调用
  3. */
  4. public static class MyMethodInvocation implements MethodInvocation {
  5. // 这是责任链
  6. protected List<MethodInterceptor> list;
  7. // 目标类
  8. protected final BaseCalculate target;
  9. public MyMethodInvocation(List<MethodInterceptor> list) {
  10. this.list = list;
  11. this.target = new BaseCalculate();
  12. }
  13. int i = 0;
  14. public Object proceed() throws Throwable {
  15. if (i == list.size()) {
  16. /**
  17. * 执行到责任链的最后一环,执行目标方法
  18. */
  19. return target.add(2,2);
  20. }
  21. MethodInterceptor interceptor = list.get(i);
  22. i++;
  23. /**
  24. * 执行责任链调用
  25. * 这个调用链第一环是: 包装后的前置通知
  26. * 调用链的第二环是: 环绕通知.
  27. * 都执行完以后,执行目标方法.
  28. */
  29. return interceptor.invoke(this);
  30. }
  31. @Override
  32. public Object getThis() {
  33. return target;
  34. }
  35. @Override
  36. public AccessibleObject getStaticPart() {
  37. return null;
  38. }
  39. @Override
  40. public Method getMethod() {
  41. try {
  42. return target.getClass().getMethod("add",int.class,int.class);
  43. } catch (NoSuchMethodException e) {
  44. e.printStackTrace();
  45. }
  46. return null;
  47. }
  48. @Override
  49. public Object[] getArguments() {
  50. return new Object[0];
  51. }
  52. }
  53. }

 

 

这里重点看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工厂的方式. 他有两个限制: 

  1. proxyfactorybean;
  2. }

 

1. 一次只能给1个类增强,如果给多个类增强就需要定义多个Proxyfactorybean

2. 增强的粒度只能到类级别上,不能指定给某个方法增强.

这样还是有一定的限制.

为了解决能够在类级别上进行增强,Spring引入了Advisor和Pointcut.

Advisor的种类有很多

  1. RegexpMethodPointcutAdvisor 按正则匹配类
  2. NameMatchMethodPointcutAdvisor 方法名匹配
  3. DefaultbeanfactoryPointcutAdvisor xml解析的Advisor
  4. InstantiationModelAwarePointcutAdvisorImpl 注解解析的advisor(@Before @After....)

我们使用按方法名的粒度来增强,所示使用的是NameMatchMethodPointcutAdvisor

  1. *
  2. * Advisor 种类很多:
  3. * RegexpMethodPointcutAdvisor 按正则匹配类
  4. * NameMatchMethodPointcutAdvisor 方法名匹配
  5. * DefaultbeanfactoryPointcutAdvisor xml解析的Advisor
  6. * InstantiationModelAwarePointcutAdvisorImpl 注解解析的advisor(@Before @After....)
  7. @Bean
  8. NameMatchMethodPointcutAdvisor aspectAdvisor() {
  9. *
  10. * 通知通知者的区别:
  11. * 通知(Advice) :是我们的通知 没有带切点
  12. * 通知者(Advisor):是经过包装后的细粒度控制方式。 带了切点
  13. NameMatchMethodPointcutAdvisor advisor = NameMatchMethodPointcutAdvisor();
  14. // 切入点增强的通知类型--前置通知
  15. advisor.setAdvice(baseBeforeAdvice());
  16. // 指定切入点方法名--div
  17. advisor.setMappedNames("div");
  18. advisor;
  19. }

 

这里设置了切入点需要增强的通知,和需要切入的方法名. 

这样就可以对类中的某个方法进行增强了.  我们依然需要使用Proxyfactorybean()代理工厂类来进行增强

  1. @Bean
  2. public Proxyfactorybean calculateProxy() {
  3. Proxyfactorybean userService = new Proxyfactorybean();
  4. userService.setInterceptorNames("aspectAdvisor");
  5. userService.setTarget(baseCalculate());
  6. return userService;
  7. }

注意,这里增强的拦截名称要写刚刚定义的 NameMatchMethodPointcutAdvisor 类型的拦截器.目标类还是我们的基础业务类baseCalculate()

这只是解决了可以对指定方法进行增强. 那么,如何能够一次对多个类增强呢? Spring又引入了ProxyCreator.

  1. * 通知通知者的区别:
  2. * 通知(Advice) :是我们的通知 没有带切点
  3. * 通知者(Advisor):是经过包装后的细粒度控制方式。 带了切点
  4. NameMatchMethodPointcutAdvisor();
  5. 切入点增强的通知类型--前置通知
  6. advisor.setAdvice(baseBeforeAdvice());
  7. 指定切入点方法名--div
  8. advisor.setMappedNames(div);
  9. advisor;
  10. }
  11. *
  12. * autoProxy: BeanPostProcessor 手动指定Advice方式,* @return
  13. */ BeanNameAutoProxyCreator autoProxyCreator() {
  14. 使用bean名字进行匹配
  15. BeanNameAutoProxyCreator beanNameAutoProxyCreator = BeanNameAutoProxyCreator();
  16. beanNameAutoProxyCreator.setBeanNames(base* 设置拦截链的名字
  17. beanNameAutoProxyCreator.setInterceptorNames(aspectAdvisor beanNameAutoProxyCreator;
  18. }

 

如上代码beanNameAutoProxyCreator.setBeanNames("base*"); 表示按照名字匹配以base开头的类,对其使用的拦截器的名称aspectAdvisor

 而aspectAdvisor使用的是按照方法的细粒度进行增强. 这样就实现了,对以base开头的类,对其中的某一个或某几个方法进行增强. 使用的增强类是前置通知.

下面修改main方法,看看运行效果

  1. );
  2. IBaseCalculate calculate = context.getBean(baseCalculate);
  3. calculate.add(******************);
  4. calculate.div(3);
  5. }
  6. }

 

这里面执行了两个方法,一个是add(),另一个是div(). 看运行结果

  1. 执行目标方法: add
  2. ******************
  3. ===========进入beforeAdvice()============
  4. 前置通知--即将进入切入点方法
  5. ===========进入beforeAdvice() 结束============
  6. 执行目标方法: 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的种类有很多. 上面代码提到过的有:

  1. /**
  2. * Advisor 种类很多:
  3. * RegexpMethodPointcutAdvisor 按正则表达式的方式匹配类
  4. * NameMatchMethodPointcutAdvisor 按方法名匹配
  5. * DefaultbeanfactoryPointcutAdvisor xml解析的Advisor
  6. * InstantiationModelAwarePointcutAdvisorImpl 注解解析的advisor(@Before @After....)
  7. */

 

而我们注解里面是按照execution表达式的方式进行匹配的

 第三步: 调用. 调用的时候,循环里面所有的通知. 最后输出结果.

 调用的类,如果已经生成了动态代理. 那么调用方法,就是动态代理生成方法了.然后拿到所有的advisor,作为一个责任链调用. 执行各类通知,最后返回执行结果

 

猜你在找的Spring相关文章