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

目標:

1.什麼是AOP, 什麼是AspectJ,spring

2. 什麼是Spring AOP數據庫

3. Spring AOP註解版實現原理編程

4. Spring AOP切面原理解析安全


 一. 認識AOP

1.1 什麼是AOP

aop是面向切面編程,相比傳統oop,aop可以在方法的前置,中置,後置中插入邏輯代碼,對於項目中大量邏輯重複的代碼,使用aop能很好的收口邏輯,將邏輯獨立於業務代碼以外,一處編寫,多處使用。ide

AOP是Object Oriented Programming(OOP)的補充.函數

OOP可以很好地解決對象的數據和封裝的問題,卻不能很好的解決Aspect("方面")分離的問題。下面舉例具體說明。oop

好比,咱們有一個Bank(銀行)類。Bank有兩個方法,save(存錢)和withdraw(取錢)。gradle

類和方法的定義以下:ui

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類的每一個重要方法加上安全認證特性。spa

因而, 咱們在兩個方法上增長安全代碼

改後的類和方法以下:

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

相關文章
相關標籤/搜索