阅读 629

AOP-实现原理

一、理论基础知识

这里先恶补一下AOP的基础知识;

AOP:直接通俗的理解就是面向切面的编程;面向切面编程是什么在网上有很多,这里就不再进行阐述了;其实切面的真正作用是在某个已经完成的功能逻辑上使用非常松耦合的方式在执行前、执行后、异常时等场景下添加一系列的增强性逻辑处理;

就AOP而言,是通过动态代理方式来实现的;这里需要了解一下AOP的几个核心概念:

  • 切面——Aspect

重点在于关注模块化;这个关注点可能会横切多个对象;事务管理是Java企业级应用中切面的典型应用例子。在Spring Aop中切面可以使用普通类基于模式方式(schema-based approach)或者普通类中以@Aspect注解方式来实现;

  • 连接点——Join point

在Spring AOP中一个连接点代表一个方法的执行;

  • 通知——Advice

在切面的某个特定的连接点上执行的动作;通知具备around、before、after等多种类型;在包括Spring在内的很多AOP模型中,基本上通知模型都是通过拦截器来实现的;

  • 目标对象——Target

指将要被增强的目标对象,包含主业务逻辑的类对象;

  • 切点——Pointcut

匹配链接点的断言逻辑;通知与切点之间通过表达式进行关联,在满足这个切点的连接点上运行,即:某个特定名称方法执行时;切点表达式与连接点的匹配是AOP的核心,在Spring中默认使用AspectJ切点语义;

  • 织入——Weaving

将通知切入连接点的过程被成为织入;

  • 引入——Introductions

可以将其他接口和实现动态引入到targetClass中;

二、AOP注解说明

在SpringBoot中推荐使用注解的方式,那么我们这里就从注解方式开始,

  • @EnableAspectJAutoProxy

这个注解与XML标签aop:aspectj-autoproxy功能相同;

  • @Aspect

标识当前的类是一个切面处理类;

  • @Pointcut

标识切点的增强点;

  • @Before

前置处理,在切点方法执行前需要执行的处理;

  • @After

后置处理,在切点方法之后后需要执行的处理;

  • @AfterReturning

返回处理,在切点方法返回数据之后需要执行的处理;

  • @AfterThrowing

异常处理,在切点方法执行出现异常时需要执行的处理;

  • @DeclareParents

引入一个类,这个类可以动态的加入一个对象,在使用ApplicationContext.getBean的时候可以使用这个加入的对象来接收并执行方法,这种方法使用比较少,另外这种方法引入了之后不会被前置后置等处理进行增强;

三、Spring AOP与AspectJ

其实这个问题真的可以难倒英雄汉,很多概念性的东西非常模糊,整理这篇文章的时候顺便把这块的相关知识做一个阐述;

3.1)Spring AOP

在Spring中Aop是通过动态代理来实现的。如果是接口,则使用JDK提供的动态代理来实现;如果没有接口,则使用CGLIB来实现;

在Spring3.2之后在SpringCore中直接把CGLIB与ASM的源码包括进来了,这也是不需要显示的引入这两个依赖的原因;

在Spring中AOP是需要依赖IOC容器来进行管理的;

在Spring中还提供了AspectJ的支持,但是仅仅用到了AspectJ的切点解析和匹配,即:AspectJ表达式;

注:Spring的AOP是基于代理实现的,在容器启动的时候需要生成代理的实例,在方法调用上也会增加栈的深度,所以SpringAOP的性能不如AspectJ那么好;

这里重点就是声明了一个早期的事件监听器和事件,不需要开发人员手动的调用publishEvent方法来发布事件,由Spring来完成发布,那么Spring是在什么时候发布呢?大爷您不要急嘛,慢慢来,往下看;

3.2)AspectJ

来自Eclipse基金会(www.eclipse.org/aspectj)

AspectJ属于静态织入,即:通过修改代码来实现的;具体的织入时机如下:

  • Compile-time weaving:编译期织入,例如:一个类使用了AspectJ添加了一个属性,如果另外一个类引用了它,这个场景就需要在编译的时候进行织入,否则无法编译引用类;
  • Post-compile weaving:编译后织入,通俗的讲就是已经生成了class文件或者已经打成了Jar包,如果这时需要增强的话,就要用到编译后织入;
  • Load-time weaving:在加载类的时候织入,这个时期的织入通常使用的方法如下:
    • 1、自定义一个类加载器来完整,这个是比较常见的方案,在被织入类加载到JVM前对它进行加载,这样就可以在加载的时候自定义行为了;
    • 2、在JVM启动的时候指定AspectJ提供的agent方法(-javaagent:【jar的路径】),这个方法不常用;

其实AspectJ能处理很多Spring AOP做不了的事情,可以说是AOP编程的完全解决方案;区别在于Spring AOP致力于解决企业级开发中最普遍的AOP需求(方法织入),而不是成为一个AOP编程完全的解决方案;

注:AspectJ在实际代码运行前完成了织入,所以大家会说他生成的类是没有额外开销的;另外,如果要使用AspectJ那么就必须要使用AspectJ自己的编译解析器,所以开发成本增加了;

3.3)总结

在Spring的AOP中仅仅是提供了AspectJ的支持,仅仅使用了切点部分的注解,及延用了一些AspectJ的概念,是因为Spring作为一个生态,它更加关心的是满足及解决企业级应用开发中的实际需求,简化而切合实际需求,并不是为了AOP而AOP;

四、Spring AOP的起航

Spring 1.2:基于接口的配置,最早的Spring AOP是完全基于几个特定的接口;

Spring 2.0:schema-based配置,在2.0以后开始使用XML的方式来进行配置和管理(标签);

Spring 2.0:@AspectJ配置,使用注解方式来进行配置,这种方式使用起来非常方便,注意这里的@AspectJ与真正的AspectJ没有半毛钱关系;

示例代码如下:

测试使用的业务处理:

public class TestManager {

    /**
     * 功能描述:测试方法
     * @date : 2021/4/20 0020 下午 9:23
     */
    public void add(int a, int b){
        System.out.println("参数运算结果:"+ (a+b));
    }

}
复制代码

前置通知增强类,注意MethodBeforeAdvice这个接口

public class TestBeforeAdvice implements MethodBeforeAdvice {
    /**
     * Callback before a given method is invoked.
     *
     * @param method method being invoked
     * @param args   arguments to the method
     * @param target target of the method invocation. May be {@code null}.
     * @throws Throwable if this object wishes to abort the call.
     *                   Any exception thrown will be returned to the caller if it's
     *                   allowed by the method signature. Otherwise the exception
     *                   will be wrapped as a runtime exception.
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置拦截——执行对象:"+ target + ";执行方法:"+ method.getName() + ";入参:"+ Arrays.asList(args));
    }
}
复制代码

环绕处理增强类,注意MethodInterceptor这个接口

public class TestInterceptor implements MethodInterceptor {

    /**
     * Implement this method to perform extra treatments before and
     * after the invocation. Polite implementations would certainly
     * like to invoke {@link Joinpoint#proceed()}.
     *
     * @param invocation the method invocation joinpoint
     * @return the result of the call to {@link Joinpoint#proceed()};
     * might be intercepted by the interceptor
     * @throws Throwable if the interceptors or the target object
     *                   throws an exception
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("环绕拦截——执行对象:"+getClass()+"参数:"+ Arrays.asList(invocation.getArguments()));
        /*
        * 注意,这里invocation.proceed()是责任链模式;
        * 会不断的往下递归调用,直到最后一个节点为止
        * */
        final Object proceed = invocation.proceed();
        System.out.println("环绕拦截——执行对象:"+getClass()+"参数:"+ Arrays.asList(invocation.getArguments()));
        return proceed;
    }
}
复制代码

配置类:

public class AopConfig {

    /**
     * 功能描述:注入测试对象
     *
     * @date : 2021/4/20 0020 下午 8:54
     */
    @Bean
    public TestManager testManagerBean() {
        return new TestManager();
    }

    /**
     * 功能描述:注入前置处理器
     *
     * @date : 2021/4/20 0020 下午 8:54
     */
    @Bean
    public TestBeforeAdvice testBeforeAdvice() {
        return new TestBeforeAdvice();
    }

    /**
     * 功能描述:注入环绕通知
     *
     * @date : 2021/4/20 0020 下午 8:59
     */
    @Bean
    public TestInterceptor testInterceptor() {
        return new TestInterceptor();
    }

    /**
     * 功能描述:注入代理工厂Bean
     * ProxyFactoryBean这个Bean大家看名称就应该能够猜到是一个可以生产代理Bean的类
     * 这种方式是最早期的AOP(没有引入AspectJ的时候)实现方式
     * 这种方法是一种煎熬,不难发现一次只能拦截增强一个类,如果你要增强多个类,需要创建多个ProxyFactoryBean
     * 使用起来非常的麻烦
     * 这里需要特别注意一下ProxyFactoryBean,在之前的章节中讲过,这是一个特殊的类,
     * 在IOC容器加载的时候并不是加载的ProxyFactoryBean,而是调用其中的getObject方法返回的类
     * @date : 2021/4/20 0020 下午 9:06
     */
    @Bean
    public ProxyFactoryBean proxyFactoryBean() {
        final ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        //设置需要增强的处理器,这里设置了之后,在执行的时候是通过责任链模式依次进行调用的;
        proxyFactoryBean.setInterceptorNames("testBeforeAdvice", "testInterceptor");
        //指定需要拦截的Bean
        proxyFactoryBean.setTarget(testManagerBean());
        return proxyFactoryBean;
    }
}
复制代码

启动类:

public class TestMain {
    public static void main(String[] values){
        final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
        final TestManager proxyFactoryBean = annotationConfigApplicationContext.getBean("proxyFactoryBean", TestManager.class);
        System.out.println("调用的对象为:"+proxyFactoryBean.getClass());
        proxyFactoryBean.add(1,2);
    }
}
复制代码

最终结果:

调用的对象为:class xuexi.aop.test.TestManager$$EnhancerBySpringCGLIB$$455ba05c
前置拦截——执行对象:xuexi.aop.test.TestManager@4c9f8c13;执行方法:add;入参:[1, 2]
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
参数运算结果:3
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
复制代码

这里特别注意一下ProxyFactoryBean这个对象,这个对象从名字上就可以看到是一个用于生产代理的工厂Bean,在Spring中这类的Bean是特殊的,IOC容器再加载的时候不会加载本身而是调用其getObject方法获得Bean;那么产生代理的核心逻辑就在这里了;

/**
 * Return a proxy. Invoked when clients obtain beans from this factory bean.
 * Create an instance of the AOP proxy to be returned by this factory.
 * The instance will be cached for a singleton, and create on each call to
 * {@code getObject()} for a proxy.
 * @return a fresh AOP proxy reflecting the current state of this factory
 */
@Override
@Nullable
public Object getObject() throws BeansException {
   /*
   * 这里使用的是责任链模式拿到配置的所有注入的增强对象
   * 责任链的Demo看我之前的章节吧;
   * */
   initializeAdvisorChain();
   if (isSingleton()) {
      /*
      * 这里返回的其实就是已经创建好的动态代理对象
      * */
      return getSingletonInstance();
   }
   else {
      if (this.targetName == null) {
         logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
               "Enable prototype proxies by setting the 'targetName' property.");
      }
      return newPrototypeInstance();
   }
}
复制代码

在源码中initializeAdvisorChain方法就是一个典型的责任链调用处理;调用的是通过setInterceptorNames方法加入的增强器,而且是依次调用;在责任连使用递归调用的时候肯定不会乱来,必定是有共性的才可以调用,由此可以推断出MethodInterceptor、MethodBeforeAdvice等特定的内置接口具有一个相同的父类;(PS:如果不是同一个老子的话,乱串门会出事的

clipboard.png

现在将之前的测试业务处理类修改一下:

public class TestManager {

    /**
     * 功能描述:测试方法——加法
     * @date : 2021/4/20 0020 下午 9:23
     */
    public void add(int a, int b){
        System.out.println("参数运算结果:"+ (a+b));
    }

    /**
     * 功能描述:测试方法——减法
     * @date : 2021/4/20 0020 下午 9:23
     */
    public void subtraction(int a, int b){
        System.out.println("参数运算结果:"+ (a-b));
    }

}
复制代码

修改一下启动方法:

public class TestMain {
    public static void main(String[] values){
        final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
        final TestManager proxyFactoryBean = annotationConfigApplicationContext.getBean("proxyFactoryBean", TestManager.class);
        System.out.println("调用的对象为:"+proxyFactoryBean.getClass());
        proxyFactoryBean.subtraction(1,2);
    }
}
复制代码

结果如下:

调用的对象为:class xuexi.aop.test.TestManager$$EnhancerBySpringCGLIB$$455ba05c
前置拦截——执行对象:xuexi.aop.test.TestManager@4c9f8c13;执行方法:subtraction;入参:[1, 2]
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
参数运算结果:-1
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
复制代码

由此可见这种方式的AOP颗粒度只能控制到类,不能控制到方法;那么如何将颗粒度更加细腻的控制在方法上呢?(还是那句老话:大爷,您别急嘛!)Spring更加关注的是企业级应用的需求,所以提供给开发人员一个增强的处理类:NameMatchMethodPointcutAdvisor;(PS:请大家记住这最后一个单词Advisor;非常重要!非常重要!非常重要!重要的事情说三遍;)修改一下配置类:

public class AopConfig {

    /**
     * 功能描述:注入测试对象
     *
     * @date : 2021/4/20 0020 下午 8:54
     */
    @Bean
    public TestManager testManagerBean() {
        return new TestManager();
    }

    /**
     * 功能描述:注入前置处理器
     *
     * @date : 2021/4/20 0020 下午 8:54
     */
    @Bean
    public TestBeforeAdvice testBeforeAdvice() {
        return new TestBeforeAdvice();
    }

    /**
     * 功能描述:注入环绕通知
     *
     * @date : 2021/4/20 0020 下午 8:59
     */
    @Bean
    public TestInterceptor testInterceptor() {
        return new TestInterceptor();
    }

    @Bean
    public NameMatchMethodPointcutAdvisor nameMatchMethodPointcutAdvisor(){
        final NameMatchMethodPointcutAdvisor nameMatchMethodPointcutAdvisor = new NameMatchMethodPointcutAdvisor();
        //加入增强器
        nameMatchMethodPointcutAdvisor.setAdvice(testBeforeAdvice());
        //加入方法名
        nameMatchMethodPointcutAdvisor.setMappedName("subtraction");
        return nameMatchMethodPointcutAdvisor;
    }

    @Bean
    public ProxyFactoryBean proxyFactoryBean() {
        final ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        //设置需要增强的处理器,这里设置了之后,在执行的时候是通过责任链模式依次进行调用的;
        proxyFactoryBean.setInterceptorNames("nameMatchMethodPointcutAdvisor");
        //指定需要拦截的Bean
        proxyFactoryBean.setTarget(testManagerBean());
        return proxyFactoryBean;
    }
}
复制代码

修改一下启动类:

public class TestMain {
    public static void main(String[] values){
        final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
        final TestManager proxyFactoryBean = annotationConfigApplicationContext.getBean("proxyFactoryBean", TestManager.class);
        System.out.println("调用的对象为:"+proxyFactoryBean.getClass());
        proxyFactoryBean.add(1,2);
        proxyFactoryBean.subtraction(1,2);
    }
}
复制代码

执行结果:

参数运算结果:3
前置拦截——执行对象:xuexi.aop.test.TestManager@a9cd3b1;执行方法:subtraction;入参:[1, 2]
参数运算结果:-1
复制代码

从结果中可以看到对方法add已经没有增强了;反而对subtraction方法进行了增强;

Advisor的种类有很多,大致如下:

  • RegexpMethodPointcutAdvisor:按正则表达式匹配;
  • NameMatchMethodPointcutAdvisor:按方法名称匹配;
  • DefaultBeanFactoryPointcutAdvisor:基于XML配置的Advisor,例如:aop:before;
  • InstantiationModelAwarePointcutAdvisorImpl:注解解析的Advisor,例如:@Before、@After等等;

为什么之前说这个Advisor是重点,因为在现在的开发中Spring会把@Before、@After等注解的通知方法都封装成一个对应的Advisor,统一的Advisor对象:InstantiationModelAwarePointcutAdvisorImpl;在这个对象中有一个AspectJExpressionPointcut对象,由这个对象来解析切点;上面的例子中我们是手动指定的一个NameMatchMethodPointcutAdvisor,那么在Spring扫描的过程中遇到满足条件的方法或类的时候会根据情况封装成不同的Advisor;

到了这里说了一堆,还是没有彻底的解决针对每个类创建一个ProxyFactoryBean的尿性;那么如何解决这个问题呢?这个同样是企业级应用开发的一个需求,Spring当然不会不管;在Spring中同时又引入了一个AutoProxyCreator的概念;这个AutoProxyCreator就是利用Bean的后置处理器(BeanPostProcessor)概念;

PS:Bean的后置处理器的概念不用我再在这里瞎逼逼了吧,不明白的看看我之前的文章;

clipboard.png

现在先添加一个接口(个人习惯,多个类具备统一的处理就有一个统一的接口):

public interface ITestManager {
    /**
     * 功能描述:加法运算
     * @date : 2021/4/20 0020 下午 10:36
     */
    void add(int a, int b);
    /**
     * 功能描述:减法运算
     * @date : 2021/4/20 0020 下午 10:36
     */
    void subtraction(int a, int b);
}
复制代码

修改测试业务类:

public class TestManager implements ITestManager {

    /**
     * 功能描述:测试方法
     * @date : 2021/4/20 0020 下午 9:23
     */
    @Override
    public void add(int a, int b){
        System.out.println("参数运算结果:"+ (a+b));
    }

    /**
     * 功能描述:测试方法
     * @date : 2021/4/20 0020 下午 9:23
     */
    @Override
    public void subtraction(int a, int b){
        System.out.println("参数运算结果:"+ (a-b));
    }
}
复制代码

再创建一个测试业务类,用于看效果

public class TestManagerTwo implements ITestManager {

    /**
     * 功能描述:测试方法
     * @date : 2021/4/20 0020 下午 9:23
     */
    @Override
    public void add(int a, int b){
        System.out.println("参数运算结果:"+ ((a+b)*10));
    }

    /**
     * 功能描述:测试方法
     * @date : 2021/4/20 0020 下午 9:23
     */
    @Override
    public void subtraction(int a, int b){
        System.out.println("参数运算结果:"+ ((a-b)*10));
    }
}
复制代码

修改一下配置类

public class AopConfig {

    /**
     * 功能描述:注入测试对象
     *
     * @date : 2021/4/20 0020 下午 8:54
     */
    @Bean
    public ITestManager testManager() {
        return new TestManager();
    }

    /**
     * 功能描述:注入测试对象
     *
     * @date : 2021/4/20 0020 下午 8:54
     */
    @Bean
    public ITestManager testManagerTwo() {
        return new TestManagerTwo();
    }

    /**
     * 功能描述:注入前置处理器
     *
     * @date : 2021/4/20 0020 下午 8:54
     */
    @Bean
    public TestBeforeAdvice testBeforeAdvice() {
        return new TestBeforeAdvice();
    }

    /**
     * 功能描述:注入环绕通知
     *
     * @date : 2021/4/20 0020 下午 8:59
     */
    @Bean
    public TestInterceptor testInterceptor() {
        return new TestInterceptor();
    }

    @Bean
    public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){
        final BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
        //需要创建代理的所有Bean名称,这里使用通配符
        beanNameAutoProxyCreator.setBeanNames("testManager*");
        //设置拦截增强器,注意这里加入的增强器是有顺序的,是按输入的先后顺序来的
        beanNameAutoProxyCreator.setInterceptorNames("testBeforeAdvice", "testInterceptor");
        return beanNameAutoProxyCreator;
    }
}
复制代码

最后修改启动类:

public class TestMain {
    public static void main(String[] values){
        final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
        ITestManager testManager = (ITestManager) annotationConfigApplicationContext.getBean("testManager");
        System.out.println("调用的对象为:"+testManager.getClass());
        testManager.add(1,2);
        testManager.subtraction(1,2);

        testManager = (ITestManager) annotationConfigApplicationContext.getBean("testManagerTwo");
        System.out.println("调用的对象为:"+testManager.getClass());
        testManager.add(1,2);
        testManager.subtraction(1,2);
    }
}
复制代码

执行结果

前置拦截——执行对象:xuexi.aop.test.TestManager@5b0abc94;执行方法:add;入参:[1, 2]
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
参数运算结果:3
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
前置拦截——执行对象:xuexi.aop.test.TestManager@5b0abc94;执行方法:subtraction;入参:[1, 2]
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
参数运算结果:-1
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
调用的对象为:class com.sun.proxy.$Proxy8
前置拦截——执行对象:xuexi.aop.test.TestManagerTwo@75c072cb;执行方法:add;入参:[1, 2]
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
参数运算结果:30
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
前置拦截——执行对象:xuexi.aop.test.TestManagerTwo@75c072cb;执行方法:subtraction;入参:[1, 2]
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
参数运算结果:-10
环绕拦截——执行对象:class xuexi.aop.test.TestInterceptor参数:[1, 2]
复制代码

看看是不是已经全部都被代理了;

五、AOP注解方式实现原理

5.1)基本流程

回顾上面的一堆示例,是不是感觉已经非常接近现在使用的效果了呢?当然还是有些不一样的地方,现在使用的是全注解方式,上面提到过,最终在IOC容器加载的时候会根据注解生成不同的Advisor;在Spring中大概的处理流程如下:

clipboard.png

这里回扣一下之前的主题”Bean后置处理器(BeanPostProcessor)“部分的内容;所以在Spring中IOC是核心,是基石,其他的一切都是通过开放的扩展点来完成的;即具备了高效的扩展,便于开发、维护;

5.2)加载逻辑

说句实在话,这块的内容,我真心的找了很久,虽然说是调用的Bean后置处理器(BeanPostProcessor)但是一直找不到注入的入口,一直揉拧着我的灵魂,好几次都想砸键盘;无意间发现了一个文件,开辟了一个新的天帝,目前还在继续深入研究中;

注意一下:Spring源码中的spring-autoconfigure-metadata.properties这个文件(具体的自己找度娘吧),这个文件是Spring自动装配时使用的配置文件,在这个文件中有一段:

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.ConditionalOnClass=org.aspectj.lang.annotation.Aspect,org.aspectj.lang.reflect.Advice,org.aspectj.weaver.AnnotatedElement,org.springframework.context.annotation.EnableAspectJAutoProxy

这里啰嗦一下:启动注解:@SpringBootApplication——>开始自动装配的注解:@EnableAutoConfiguration——>自动装配导入处理类:AutoConfigurationImportSelector——>方法:selectImports

注意最后一个EnableAspectJAutoProxy,这是一个注解中的@Import中引入了一个AspectJAutoProxyRegistrar类;

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

   /**
    * Register, escalate, and configure the AspectJ auto proxy creator based on the value
    * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
    * {@code @Configuration} class.
    */
   @Override
   public void registerBeanDefinitions(
         AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      /*
      * 向容器中注册一个名称为:
      * org.springframework.aop.config.internalAutoProxyCreator的AnnotationAwareAspectJAutoProxyCreator对象
      * 跟进去就可以看到;
      * 这是一个后置处理器对象,就是专门用来解析使用@Aspectj注解修饰的类,由于这个对象实现了InstantiationAwareBeanPostProcessor接口,
      * 所以这个后置处理器也就是在创建类createBean方法中最先调用;
      * 同时这个对象还实现了BeanPostProcessor接口,所以在初始化之后还是被调用一次
      * */
      AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

      AnnotationAttributes enableAspectJAutoProxy =
            AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
      if (enableAspectJAutoProxy != null) {
         if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
         }
         if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
         }
      }
   }

}
复制代码

这个类继承ImportBeanDefinitionRegistrar说明是一个带注册功能的Bean定义注册器;往Spring容器中注册了一个AnnotationAwareAspectJAutoProxyCreator对象,来看看这个屌丝的类结构图

clipboard.png

这个AnnotationAwareAspectJAutoProxyCreator逆天的很,同时实现了InstantiationAwareBeanPostProcessor、SmartInstantiationAwareBeanPostProcessor、BeanPostProcessor这三个非常重要的接口;

在进入创建Bean的方法createBean的时候优先会调用1次InstantiationAwareBeanPostProcessor类型的后置处理器(注意:这里再次强调在createBean方法的整个执行过程中会调用9次后置处理器),也就是在这个时候开始处理使用@Aspectj注解的类,结合之前讲到的,就是在这次调用的时候这里会将满足条件的类中@Before、@After等注解包装成一个Advisor对象;

SmartInstantiationAwareBeanPostProcessor这个类型的后置处理器是专门用于处理循环依赖的,不用多解释了; BeanPostProcessor这是在初始化后调用的后置处理器接口,在也一步调用的时候开始判断当前的类是否满足代理增强的条件,如果满足,这里则开始创建动态代理类了;

5.3)第一次调用

进入createBean之后的第一次调用的是InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation方法;

这个org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation方法的源码之前的章节已经做了详细的说明,这里只说重点

clipboard.png

这里特别注意一下shouldSkip这个方法,正常情况下这个方法是直接返回false的;但是结合AnnotationAwareAspectJAutoProxyCreator这个类来看就不是那么简单了,因为在这个类中对shouldSkip方法进行了重写,在这里完成了对@Aspectj的对象进行了解析;下面看看AnnotationAwareAspectJAutoProxyCreator中的shouldSkip方法(org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator#shouldSkip)

@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
   /**
    * 找到候选的Advisors(通知者也可以说成增强器对象)
    */
   List<Advisor> candidateAdvisors = findCandidateAdvisors();
   for (Advisor advisor : candidateAdvisors) {
      if (advisor instanceof AspectJPointcutAdvisor &&
            ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
         return true;
      }
   }
   return super.shouldSkip(beanClass, beanName);
}
复制代码
@Override
protected List<Advisor> findCandidateAdvisors() {
   //找出事务相关的advisor
   List<Advisor> advisors = super.findCandidateAdvisors();
   //找出Aspect相关的信息之后封装为一个advisor
   if (this.aspectJAdvisorsBuilder != null) {
      //获取容器中所有的切面缓存对象
      advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
   }
   //返回我们所有的通知
   return advisors;
}
复制代码

这个buildAspectJAdvisors方法就是处理的核心,这里只是列出一个大概,调用链变态的深,想挑战一下憋尿世界记录的朋友可以跟进一下;

/**
 * 去容器中获取到所有的切面信息保存到缓存中
 * @return the list of {@link org.springframework.aop.Advisor} beans
 * @see #isEligibleBean
 */
public List<Advisor> buildAspectJAdvisors() {
   /**
    * 用于保存切面的名称,该地方aspectNames 是我们的类级别的缓存,用户缓存已经解析出来的切面信息
    */
   List<String> aspectNames = this.aspectBeanNames;
   //缓存字段aspectNames没有值 表示实例化第一个单实例bean的时候就会触发解析切面的操作
   if (aspectNames == null) {
      //做了dcl检查
      synchronized (this) {
         aspectNames = this.aspectBeanNames;
         if (aspectNames == null) {
            //用于保存所有解析出来的Advisors集合对象
            List<Advisor> advisors = new ArrayList<>();
            //用于保存切面的名称的集合
            aspectNames = new ArrayList<>();
            /**
             * aop功能中在这里传入的是Object对象,代表去容器中获取到所有的组件的名称,然后再逐一的进行遍历
             * ,这个过程是十分消耗性能的,所以说spring会再这里加入了保存切面信息的缓存。
             * 注意一下:事务功能不一样,事务模块的功能是直接去容器中获取Advisor类型的,选择范围小,切不消耗性能。所以
             * spring在事务模块中没有加入缓存来保存我们的事务相关的advisor
             */
            String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                  this.beanFactory, Object.class, true, false);
            //遍历我们从IOC容器中获取处的所有bean的名称
            for (String beanName : beanNames) {
               if (!isEligibleBean(beanName)) {
                  continue;
               }
               //通过beanName去容器中获取到对应class对象
               Class<?> beanType = this.beanFactory.getType(beanName);
               if (beanType == null) {
                  continue;
               }
               //根据class对象判断是不是切面
               if (this.advisorFactory.isAspect(beanType)) {
                  /*
                  * 加入到缓存中;
                  * 这里特别注意一下在isAspect中还会排除AspectJ生成的代理对象,因为AspectJ生成的代理对象都是ajc$开头的
                  * */
                  aspectNames.add(beanName);
                  //把beanName和class对象构建成为一个AspectMetadata
                  AspectMetadata amd = new AspectMetadata(beanType, beanName);
                  if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {

                     //构建切面注解的实例工厂
                     MetadataAwareAspectInstanceFactory factory =
                           new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                     /*
                     * 真正的去获取我们的实例工厂
                     * 在getAdvisors中会拿到切面类中所有通过@Before、@After等注解修饰的方法,
                     * 并将其解析成一个Advisor类型的包装类
                     * */
                     List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                     //加入到缓存中
                     if (this.beanFactory.isSingleton(beanName)) {
                        this.advisorsCache.put(beanName, classAdvisors);
                     }
                     else {
                        this.aspectFactoryCache.put(beanName, factory);
                     }
                     advisors.addAll(classAdvisors);
                  }
                  else {
                     // Per target or per this.
                     if (this.beanFactory.isSingleton(beanName)) {
                        throw new IllegalArgumentException("Bean with name '" + beanName +
                              "' is a singleton, but aspect instantiation model is not singleton");
                     }
                     MetadataAwareAspectInstanceFactory factory =
                           new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                     this.aspectFactoryCache.put(beanName, factory);
                     advisors.addAll(this.advisorFactory.getAdvisors(factory));
                  }
               }
            }
            this.aspectBeanNames = aspectNames;
            return advisors;
         }
      }
   }

   if (aspectNames.isEmpty()) {
      return Collections.emptyList();
   }
   /**
    * 真正的创建切面的时候,我们不需要去解析了而是直接去缓存中获取处
    */
   List<Advisor> advisors = new ArrayList<>();
   for (String aspectName : aspectNames) {
      List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
      if (cachedAdvisors != null) {
         advisors.addAll(cachedAdvisors);
      }
      else {
         MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
         //解析每个注解的核心方法getAdvisors
         advisors.addAll(this.advisorFactory.getAdvisors(factory));
      }
   }
   return advisors;
}
复制代码

PS:这里之前有个哥们问我那么多的注解,他们解析是有顺序的吗?是一个什么样的顺序呢?当时我真心的不确定,后来看到这里的源码以后发现,真的是由顺序的,具体的描述地方在:org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory#ASPECTJ_ANNOTATION_CLASSES这个属性中,是根据这个属性的顺序一次开始处理的;

private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
      Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
复制代码

OK!先到这里吧;

文章分类
后端
文章标签