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

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

概述目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 目标:

1. 什么是AOP,什么是AspectJ

2. 什么是Spring AOP

3. Spring AOP注解版实现原理

4. Spring AOP切面原理解析

 一. 认识AOP及其使用

详见博文1: 5.1 Spring5源码--Spring AOP源码分析

 

二. AOP的特点 2.1 Spring AOP2.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解析的AdvisorInstantiationModelAwarepointcutAdvisorImpl 注解解析的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开头的类,对其中的某一个或某几个方法进行增强. 使用的增强类是前置通知.

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

);        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....)*/

 

而我们注解里面是按照execution表达式的方式进行匹配的 第三步: 调用. 调用的时候,循环里面所有的通知. 最后输出结果.

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

  总结

以上是内存溢出为你收集整理的5.2 spring5源码--spring AOP源码分析二--切面的配置方式 5.1 Spring5源码--Spring AOP源码分析一全部内容,希望文章能够帮你解决5.2 spring5源码--spring AOP源码分析二--切面的配置方式 5.1 Spring5源码--Spring AOP源码分析一所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1215410.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-05
下一篇 2022-06-05

发表评论

登录后才能评论

评论列表(0条)

保存