Spring 詳解(一)——- AOP前序

  • 2019 年 10 月 6 日
  • 筆記

目錄


1. AOP 簡介

​ AOP(Aspect Oriented Programming),通常稱為面向切面編程。它利用一種稱為"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行為封裝到一個可重用模組,並將其命名為"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻為業務模組所共同調用的邏輯或責任封裝起來,便於減少系統的重複程式碼,降低模組之間的耦合度,並有利於未來的可操作性和可維護性。

2. 示例需求

想要為寫好的 ArithmeticCalculator 添加日誌 ,即每次運算前後添加 採用以下方法太過繁瑣,修改內容需要每個跟著都修改,可維護性差

public interface ArithmeticCalculator {      int add(int i, int j);      int sub(int i, int j);        int mul(int i, int j);      int div(int i, int j);  }
public class MyArithmeticCalculatorImp implements ArithmeticCalculator {      public int add(int i, int j) {          System.out.println("The method add begins with["+i+","+j+"]");          int result = i + j;          System.out.println("The method add ends with["+result+"]");          return result;        }        public int sub(int i, int j) {          System.out.println("The method sub begins with["+i+","+j+"]");          int result = i - j;          System.out.println("The method sub ends with["+result+"]");          return result;      }        public int mul(int i, int j) {          System.out.println("The method mul begins with["+i+","+j+"]");          int result = i * j;          System.out.println("The method mul ends with["+result+"]");          return result;      }        public int div(int i, int j) {          System.out.println("The method div begins with["+i+","+j+"]");          int result = i / j;          System.out.println("The method div ends with["+result+"]");          return result;      }  }

結果

The method add begins with[1,2]  The method add ends with[3]  -->3  The method mul begins with[5,2]  The method mul ends with[10]  -->10

問題: 程式碼混亂:越來越多的非業務需求(日誌和驗證等)加入後,原有的業務方法急劇膨脹,每個方法在處理核心邏輯的同時還必須兼顧替他多個關注點。

程式碼分散:以日誌需求為例,只是為了滿足這個單一需求,就不得不在多個模組(方法)里多次重複相同的日誌程式碼,如果日誌需求發生變化,必須修改所有模組。

3. 解決方法一:使用靜態代理

創建乾淨的實現類

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {      @Override      public int add(int i, int j) {          return i + j;      }      @Override      public int sub(int i, int j) {          return i - j;      }      @Override      public int mul(int i, int j) {          return i * j;      }      @Override      public int div(int i, int j) {          return i / j;      }  }

創建日誌類 MyLogger

/**   * 創建日誌類   */  public class MyLogger {      /**       * 入參日誌       * @param a       * @param b       */      public void showParam(int a, int b) {          System.out.println("The method add begins with["+a+","+b+"]");      }        /**       * 運算結果日誌       * @param result       */      public void showResult(int result) {          System.out.println("The method add ends with["+3+"]");      }  }

創建靜態代理類

/**   * 代理類   */  public class ProxyLogger implements ArithmeticCalculator {        //目標類      private ArithmeticCalculator target;        //日誌類      private MyLogger logger;        public ProxyLogger(ArithmeticCalculator target, MyLogger logger) {          this.target = target;          this.logger = logger;      }      @Override      public int add(int i, int j) {          logger.showParam(i, j);          int result =  target.add(i,j);          logger.showResult(result);          return result;      }      @Override      public int sub(int i, int j) {          logger.showParam(i, j);          int result =  target.sub(i,j);          logger.showResult(result);          return result;      }      @Override      public int mul(int i, int j) {          logger.showParam(i, j);          int result =  target.mul(i,j);          logger.showResult(result);          return result;      }      @Override      public int div(int i, int j) {          logger.showParam(i, j);          int result =  target.div(i,j);          logger.showResult(result);          return result;      }  }

結果測試

public class Main {      public static void main(String[] args) {          ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorImpl();          MyLogger logger = new MyLogger();          ProxyLogger proxy = new ProxyLogger(arithmeticCalculator, logger);          System.out.println(proxy.add(1, 9));          System.out.println(proxy.mul(3, 3));        }  }  /**  The method add begins with[1,9]  The method add ends with[3]  10  The method add begins with[3,3]  The method add ends with[3]  9  */

總結

這是一個很基礎的靜態代理,業務類 ArithmeticCalculatorImpl 只需要關注業務邏輯本身,保證了業務的重用性,這也是代理類的優點,沒什麼好說的。我們主要說說這樣寫的缺點:

  • 代理對象的一個介面只服務於一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程式規模稍大時就無法勝任了。
  • 如果介面增加一個方法,比如 ArithmeticCalculatorImpl 增加歸零 changeZero()方法,則除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了程式碼維護的複雜度。

4. 解決方法二:使用動態代理

我們去掉 ProxyLogger. 類,增加一個 ObjectInterceptor.java 類

import java.lang.reflect.InvocationHandler;  import java.lang.reflect.Method;    public class ObjectInterceptor implements InvocationHandler {        //目標類      private Object target;        //切面類(這裡指 日誌類)      private MyLogger logger;        public ObjectInterceptor(Object target, MyLogger logger) {          this.target = target;          this.logger = logger;      }        @Override      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {          //參數日誌          logger.showParam((int)args[0], (int)args[1]);          System.out.println(args[0]+"--"+args[1]);          int result = (int)method.invoke(target, args);          //結果日誌          logger.showResult(result);          return result;      }  }

測試

public class Main {      public static void main(String[] args) {          MyLogger logger = new MyLogger();          ProxyLogger proxy = new ProxyLogger(arithmeticCalculator, logger);          System.out.println(proxy.add(1, 9));          System.out.println(proxy.mul(3, 3));*/            Object object = new ArithmeticCalculatorImpl();          MyLogger logger = new MyLogger();          ObjectInterceptor objectInterceptor = new ObjectInterceptor(object, logger);          /**           * 三個參數的含義:           * 1、目標類的類載入器           * 2、目標類所有實現的介面           * 3、攔截器          */          ArithmeticCalculator calculator = (ArithmeticCalculator) Proxy.newProxyInstance(object.getClass().getClassLoader(),                  object.getClass().getInterfaces(), objectInterceptor);            System.out.println(calculator.add(1, 5));           /*              The method add begins with[1,5]              1--5              The method add ends with[3]              6           */

 那麼使用動態代理來完成這個需求就很好了,後期在 ArithmeticCalculator 中增加業務方法,都不用更改程式碼就能自動給我們生成代理對象。而且將 ArithmeticCalculator 換成別的類也是可以的。也就是做到了代理對象能夠代理多個目標類,多個目標方法。

 注意:我們這裡使用的是 JDK 動態代理,要求是必須要實現介面。與之對應的另外一種動態代理實現模式 Cglib,則不需要,我們這裡就不講解 cglib 的實現方式了。

不管是哪種方式實現動態代理。本章的主角:AOP 實現原理也是動態代理