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 實現原理也是動態代理