微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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(calculateProxyculate.out.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开头的类,对其中的某一个或某几个方法进行增强. 使用的增强类是前置通知.

下面修改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,作为一个责任链调用. 执行各类通知,最后返回执行结果

 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐