zoukankan      html  css  js  c++  java
  • 5.1 Spring5源码--Spring AOP源码分析一

    目标:

    1.什么是AOP, 什么是AspectJ,

    2. 什么是Spring AOP

    3. Spring AOP注解版实现原理

    4. Spring AOP切面原理解析


     一. 认识AOP

    1.1 什么是AOP

    aop是面向切面编程,相比传统oop,aop能够在方法的前置,中置,后置中插入逻辑代码,对于项目中大量逻辑重复的代码,使用aop能很好的收口逻辑,将逻辑独立于业务代码之外,一处编写,多处使用。

    AOP是Object Oriented Programming(OOP)的补充.

    OOP能够很好地解决对象的数据和封装的问题,却不能很好的解决Aspect("方面")分离的问题。下面举例具体说明。

    比如,我们有一个Bank(银行)类。Bank有两个方法,save(存钱)和withdraw(取钱)。

    类和方法的定义如下:

    package com.lxl.www.aop;
    
    public class Bank {
    
      /**
       * 存钱
       */
      public Float save(Account account, float money) {
        // 增加account账户的钱数,返回账户里当前的钱数
        return null;
      }
    
      /**
       * 取钱
       */
      public Float withdraw(Account account, float money) {
        // 减少account账户的钱数,返回取出的钱数
        return null;
      }
    };

    这两个方法涉及到用户的账户资金等重要信息,必须要非常小心,所以编写完上面的商业逻辑之后,项目负责人又提出了新的要求--给Bank类的每个重要方法加上安全认证特性。

    于是, 我们在两个方法上增加安全代码

    改后的类和方法如下:

    public class Bank {
    
      /**
       * 存钱
       */
      public Float save(Account account, float money) {
        // 验证account是否为合法用户
        // 增加account账户的钱数,返回账户里当前的钱数
        return null;
      }
    
      /**
       * 取钱
       */
      public Float withdraw(Account account, float money) {
        // 验证account是否为合法用户
        // 减少account账户的钱数,返回取出的钱数
        return null;
      }
    };

    这两个方法都需要操作数据库,为了保持数据完整性,项目负责人又提出了新的要求--给Bank类的每个操作数据库的方法加上事务控制。

    于是,我们不得不分别在上面的两个方法中加入安全认证的代码。

    类和方法的定义如下:

    package com.lxl.www.aop;
    
    public class Bank {
    
      /**
       * 存钱
       */
      public Float save(Account account, float money) {
        // 验证account是否为合法用户
        // begin Transaction
        // 增加account账户的钱数,返回账户里当前的钱数
        // end Transaction
        return null;
      }
    
      /**
       * 取钱
       */
      public Float withdraw(Account account, float money) {
        // 验证account是否为合法用户
        // begin Transaction
        // 减少account账户的钱数,返回取出的钱数
        // end Transaction 
        return null;
      }
    };

    我们看到,这些与商业逻辑无关的重复代码遍布在整个程序中。实际的工程项目中涉及到的类和函数,远远不止两个。如何解决这种问题?

    AOP就是为了解决这种问题而出现的。在不修改代码的情况下达到增强的效果

    1.2 AOP的相关概念

    • 切面(Aspect): 封装通用业务逻辑的组件,即我们想要插入的代码内容. 在spring AOP中, 切面可以使用通用类基于模式的方式, 或者在普通类中标注@Aspect注解来实现
    • 连接点(Join point): 连接点是在应用执行过程中能够插入切面的点。简单理解, 可以理解为需要增强的方法.
    • 通知(Advice): 用于指定具体产生作用的位置,是方法之前或之后等等
      • 前置通知(before) - 在目标方法被调用之前调用通知功能
      • 后置通知(after) - 在目标方法完成之后调用通知(不论程序是否出现异常),此时不会关心方法的输出是什么
      • 返回通知(after-returning) - 在目标方法成功执行之后调用通知
      • 异常通知(after-throwing) - 在目标方法抛出异常后调用通知
      • 环绕通知(around) - 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
    • 目标对象(target): 目标对象是指要被增强的对象, 即包含主业务逻辑的类对象
    • 切点(PointCut): 指定哪些Bean组件的哪些方法使用切面组件. 例如:当执行某个特定名称的方法时.我们定义一个切点(execution com.lxl.www.aop.*.*(..)) . 切点表达式如何和连接点匹配是AOP的核心. spring默认使用AspectJ切点语义.
    • 织入(Weaving): 将通知切入连接点过程叫做织入
    • 引入(Introductions): 可以将其它接口或者实现动态引入到targetClass中

    对照上图, 来对应每一个区域,看看其具体含义

      

    那么在Spring中使用AOP就意味着你需要哪些东西呢?我们来举个例子, 就实现上面银行的例子.
    • 首先有一个bank银行类
      package com.lxl.www.aop.bank;
      
      public interface Bank {
      
        /**
         * 存钱
         */
        Float save(Account account, float money) ;
      
        /**
         * 取钱
         */
        Float withdraw(Account account, float money);
      };
    • 有一个银行类的实现方法. 这里面save, withdraw就是连接点. 最终会将各种通知插入到连接点中
      package com.lxl.www.aop.bank;
      
      import org.springframework.stereotype.Service;
      
      /**
       * 工商银行
       *
       *
       * DATE 2020/12/6.
       *
       * @author lxl.
       */
      @Service
      public class IcbcBank implements Bank{
          @Override
          public Float save(Account account, float money) {
      // 主业务逻辑: 增加account账户的钱数,返回账户里当前的钱数 System.out.println(account.getName() + "账户存入" + money); return null; } @Override public Float withdraw(Account account, float money) {
      // 主业务逻辑: 减少account账户的钱数,返回取出的钱数 System.out.println(account.getName() + "账户取出" + money); return null; } }
    • 接下来, 要有一个切面, 切面是一个类. 切面类里面定义了切点, 通知, 引用
      package com.lxl.www.aop.bank;
      
      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.AfterThrowing;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Before;
      import org.aspectj.lang.annotation.DeclareParents;
      import org.aspectj.lang.annotation.Pointcut;
      import org.springframework.core.annotation.Order;
      import org.springframework.stereotype.Component;
      
      /**
       * 切面
       */
      @Aspect // 标记这是一个切面
      @Order
      @Component // 将其放到ioc容器管理
      public class BankLogAspect {
      
        /**
         * 引入
         *
         * 这段话可以理解为, 为com.lxl.www.aop.bank.IcbcBank 引入了一个接口 EnhanceFunctionOfBank,
         * 同时, 引入了默认的实现类 IcbcEnhanceFunctionOfBank
         */
        @DeclareParents(value = "com.lxl.www.aop.bank.IcbcBank",    // 引入的目标类. 也就是需要引入动态实现的类
                defaultImpl = IcbcEnhanceFunctionOfBank.class) // 引入的接口的默认实现
        public static EnhanceFunctionOfBank enhanceFunctionOfBank; // 引入的接口
      
      
        /**
         * 定义一个切点
         */
        @Pointcut("execution(* com.lxl.www.aop.bank.IcbcBank.*(..))")
        public void pointCut() {}
      
        /**
         * 定义一个前置通知
         * @param joinPoint
         */
        @Before(value = "pointCut()")
        public void beforeAdvice(JoinPoint joinPoint) {
          String methodName = joinPoint.getSignature().getName();
          System.out.println("执行目标方法"+methodName+"的前置通知");
        }
      
        /**
         * 定义了一个后置通知
         */
        @After(value = "pointCut()")
        public void afterAdvice(JoinPoint joinPoint) {
          String methodName = joinPoint.getSignature().getName();
          System.out.println("执行目标方法"+methodName+"的后置通知");
        }
      
        /**
         * 定义了一个返回通知
         */
        @AfterReturning(value = "pointCut()", returning = "result")
        public void returningAdvice(JoinPoint joinPoint, Object result) {
          String methodName = joinPoint.getSignature().getName();
          System.out.println("执行目标方法"+methodName+"的返回通知");
        }
      
        /**
         * 定义了一个异常通知
         */
        @AfterThrowing(value = "pointCut()")
        public void throwingAdvice(JoinPoint joinPoint) {
          String methodName = joinPoint.getSignature().getName();
          System.out.println("执行目标方法"+methodName+"的异常通知");
        }
      
      }

      那么这里的目标对象是谁呢? 就是我们的IcbcBank类. 这里需要注意的是引入: 引入的概念是将一个接口动态的让另一个类实现了. 这样实现了接口的类, 就可以动态的拥有接口实现类的功能.

    • 银行的额外功能. 也就是银行除了可以存钱, 取钱. 还有一个额外的功能. 比如理财. 不是每个银行都有的. 
      package com.lxl.www.aop.bank;
      
      /**
       * 增强的功能
       */
      public interface EnhanceFunctionOfBank {
      
          void Financialanagement(Account account);
      }
    • 具体银行额外功能的实现类
      package com.lxl.www.aop.bank;
      
      import org.springframework.stereotype.Service;
      
      /**
       * Description
       */
      @Service
      public class IcbcEnhanceFunctionOfBank implements EnhanceFunctionOfBank {
          /**
           * 理财功能
           * @param account
           */
          @Override
          public void Financialanagement(Account account) {
              System.out.println(account.getName() +"的账户 增加 理财功能");
          }
      
      }

      这个功能我们可以通过引入,动态增加到IcbcBank类中, 原本IcbcBank只有存钱和取钱的功能. 这样, 就可以增加理财功能了. 这就是引入.

    • 整体配置类
      package com.lxl.www.aop.bank;
      
      import org.springframework.beans.factory.annotation.Configurable;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.EnableAspectJAutoProxy;
      
      @Configurable
      // 使用注解的方式引入AOP
      @EnableAspectJAutoProxy
      @ComponentScan("com.lxl.www.aop.bank")
      public class BankMainConfig {
      
      }

      使用aop,需要引入AOP, 这里使用的注解的方式引入的.

    • main入口方法
      package com.lxl.www.aop.bank;
      
      import com.lxl.www.aop.Calculate;
      import com.lxl.www.aop.MainConfig;
      import com.lxl.www.aop.ProgramCalculate;
      
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      
      public class BankMainClass {
        public static void main(String[] args) {
      
          AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BankMainConfig.class);
      
          Account account = new Account("张三");
      
          Bank bank = (Bank) ctx.getBean("icbcBank");
          bank.save(account, 100);
      
          System.out.println();
          EnhanceFunctionOfBank enhanceFunctionOfBank = (EnhanceFunctionOfBank) ctx.getBean("icbcBank");
          enhanceFunctionOfBank.Financialanagement(account);
        }
      }

      如上, 运行结果:

             

    • 需要注意的地方: 是.gradle配置文件. 通常, 我们在引入AspectJ的jar包的时候, 会引入到父类项目的build.gradle中. 如下所示

      最后我们还需要引入到指定的项目中 

           

    以上就是对整个AOP的理解. 接下来, 分析AOP的源码.

    详见第二篇文章

    as

  • 相关阅读:
    最近发现一个网站
    2017-0206 委托封装的方法的参数类型
    迈向Angular 2
    趣学CCNA 路由与交换
    HCNA 2017年01月26日
    在linux中使用phpize安装php扩展模块
    接口和抽象类
    C:Program Files (x86)MSBuildMicrosoft.Cppv4.0V110Microsoft.CppCommon.targets(611,5): error MSB
    抽象类和抽象方法
    java数组与内存控制
  • 原文地址:https://www.cnblogs.com/ITPower/p/14091890.html
Copyright © 2011-2022 走看看