zoukankan      html  css  js  c++  java
  • 5.2 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, 详细可参考文章: 5.3 Spring5源码--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, @Before, @After等等. 但是, 我们使用Spring AOP是使用纯java代码写的. 也就是说他完全属于Spring, 和AspectJ没有什么关系. Spring只是沿用了AspectJ中的概念. 包括AspectJ提供的jar报的注解. 但是, 并不依赖于AspectJ的功能.

    我们使用的@Aspect, @Pointcut, @Before, @After等注解都是来自于AspectJ, 但是其功能的实现是纯Spring AOP自己实现的. 

    Spring AOP经历了三种配置方式. 

    第一种: 基于接口方式的配置. 在Spring1.2版本, 提供的是完全基于接口方式实现的

      这种方式是最古老的方式, 但由于spring做了很好的向后兼容, 所以, 现在还是会有很多代码使用这种方式, 比如:声明式事务. 

      我们要了解这种配置方式还有另一个原因, 就是我们要看源码. 源码里对接口方式的配置进行了兼容处理. 同时, 看源码的入口是从接口方式的配置开始的.

      那么, 在没有引入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 numA, int numB);
    
        int div(int numA, int numB);
    
        int multi(int numA, int numB);
    
        int mod(int numA, int 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;
    
    /**
     * 业务实现类 -- 基础计算器
     */
    
    public class BaseCalculate implements IBaseCalculate {
    
        @Override
        public int add(int numA, int numB) {
            System.out.println("执行目标方法: add");
            return numA + numB;
        }
    
        @Override
        public int sub(int numA, int numB) {
            System.out.println("执行目标方法: sub");
            return numA - numB;
        }
    
        @Override
        public int multi(int numA, int numB) {
            System.out.println("执行目标方法: multi");
            return numA * numB;
        }
    
        @Override
        public int div(int numA, int numB) {
            System.out.println("执行目标方法: div");
            return numA / numB;
        }
    
        @Override
        public int mod(int numA, int numB) {
            System.out.println("执行目标方法: 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接口
     */
    public class BaseBeforeAdvice implements MethodBeforeAdvice {
    
        /**
         *
         * @param method 切入的方法
         * @param args 切入方法的参数
         * @param target 目标对象
         * @throws Throwable
         */
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println("===========进入beforeAdvice()============");
            System.out.println("前置通知--即将进入切入点方法");
            System.out.println("===========进入beforeAdvice() 结束============
    ");
        }
    
    }

      后置通知

    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接口
     */
    public class BaseAfterReturnAdvice implements AfterReturningAdvice {
    
        /**
         *
         * @param returnValue 切入点执行完方法的返回值,但不能修改
         * @param method 切入点方法
         * @param args 切入点方法的参数数组
         * @param target 目标对象
         * @throws Throwable
         */
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println("
    ==========进入afterReturning()===========");
            System.out.println("后置通知--切入点方法执行完成");
            System.out.println("==========进入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接口
     */
    public class BaseAroundAdvice implements MethodInterceptor {
    
        /**
         * invocation :连接点
         */
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            System.out.println("===========around环绕通知方法 开始===========");
            // 调用目标方法之前执行的动作
            System.out.println("环绕通知--调用方法之前: 执行");
            // 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
            Object returnValue = invocation.proceed();
            System.out.println("环绕通知--调用方法之后: 执行");
            System.out.println("===========around环绕通知方法  结束===========");
            return returnValue;
        }
    
    }

      配置类

    package com.lxl.www.aop.interfaceAop;
    
    import org.springframework.aop.framework.ProxyFactoryBean;
    import org.springframework.context.annotation.Bean;
    
    /**
     * 配置类
     */
    public class MainConfig {
    
        /**
         * 被代理的对象
         * @return
         */
        @Bean
        public IBaseCalculate baseCalculate() {
            return new BaseCalculate();
        }
    
        /**
         * 前置通知
         * @return
         */
        @Bean
        public BaseBeforeAdvice baseBeforeAdvice() {
            return new BaseBeforeAdvice();
        }
    
        /**
         * 后置通知
         * @return
         */
        @Bean
        public BaseAfterReturnAdvice baseAfterReturnAdvice() {
            return new BaseAfterReturnAdvice();
        }
    
        /**
         * 环绕通知
         * @return
         */
        @Bean
        public BaseAroundAdvice baseAroundAdvice() {
            return new BaseAroundAdvice();
        }
    
        /**
         * 使用接口方式, 一次只能给一个类增强, 如果想给多个类增强, 需要定义多个ProxyFactoryBean
         * @return
         */
        @Bean
        public ProxyFactoryBean calculateProxy() {
            ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
            proxyFactoryBean.setInterceptorNames("baseAfterReturnAdvice", "baseBeforeAdvice", "baseAroundAdvice");
            proxyFactoryBean.setTarget(baseCalculate());
            return proxyFactoryBean;
        }
    
    }

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

      方法入口

    package com.lxl.www.aop.interfaceAop;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class InterfaceMainClass{
    
        public static void main(String[] args) {
            ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
            IBaseCalculate calculate = context.getBean("calculateProxy", IBaseCalculate.class);
            System.out.println(calculate.getClass());
            calculate.add(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(). 下面看一下源码

    public class ProxyFactoryBean extends ProxyCreatorSupport
            implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {
    ......
       @Override
        @Nullable
        public Object getObject() throws BeansException {
            /**
             * 初始化通知链: 将通知放入链中
    * 后面初始化的时候, 是通过责任链的方式调用这些通知链的的.
    * 那么什么是责任链呢?
    */ initializeAdvisorChain(); if (isSingleton()) { /** * 创建动态代理 */ return 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."); } return newPrototypeInstance(); } } ...... }

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

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

    private synchronized Object getSingletonInstance() {
            if (this.singletonInstance == null) {
                this.targetSource = freshTargetSource();
                if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
                    // Rely on AOP infrastructure to tell us what interfaces to proxy.
                    Class<?> targetClass = getTargetClass();
                    if (targetClass == null) {
                        throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
                    }
                    setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
                }
                // Initialize the shared singleton instance.
                super.setFrozen(this.freezeProxy);
                /**
                 * 创建动态代理
                 */
                this.singletonInstance = getProxy(createAopProxy());
            }
            return this.singletonInstance;
        }

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

        @Override
        public Object getProxy(@Nullable ClassLoader classLoader) {
            if (logger.isTraceEnabled()) {
                logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
            }
            Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
            findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
            /**
             * 创建动态代理
             */
            return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
        }

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

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

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

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

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

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

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

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

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

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

    下面看一个demo. 

    第二种: 基于schema-based配置. 在spring2.0以后使用了xml的方式来配置. 

    第三种: 基于注解@Aspect的方式J. 这种方式是最简单, 方便的. 这里虽然较AspectJ, 但实际上和AspectJ一点关系也没有.

    as

  • 相关阅读:
    [LintCode] Merge Two Sorted Lists 混合插入有序链表
    Convert PLY to VTK Using PCL 1.6.0 or PCL 1.8.0 使用PCL库将PLY格式转为VTK格式
    [LintCode] Best Time to Buy and Sell Stock II 买股票的最佳时间之二
    [LintCode] Maximum Subarray 最大子数组
    [LeetCode] Matchsticks to Square 火柴棍组成正方形
    [LeetCode] Sort Characters By Frequency 根据字符出现频率排序
    [LeetCode] 450. Delete Node in a BST 删除二叉搜索树中的节点
    [LeetCode] Serialize and Deserialize BST 二叉搜索树的序列化和去序列化
    [LeetCode] Find All Numbers Disappeared in an Array 找出数组中所有消失的数字
    [LeetCode] Add Two Numbers II 两个数字相加之二
  • 原文地址:https://www.cnblogs.com/ITPower/p/14091891.html
Copyright © 2011-2022 走看看