7

5.1 Spring5源码--Spring AOP源码分析一

 3 years ago
source link: http://www.cnblogs.com/ITPower/p/14091890.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

目标:

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中
AzaamyU.png!mobile

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

eIFRZvm.png!mobile

那么在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);
      }
    }
    

    如上, 运行结果:

VBRVj2B.png!mobile

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

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

FFFJbiM.png!mobile

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

详见第二篇文章

as


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK