1. 程式人生 > 實用技巧 >第三章、AOP前奏

第三章、AOP前奏

一、提出問題

1.1、情景:數學計算器

要求

  • 執行加減乘除運算

  • 日誌:在程式執行期間追蹤正在發生的活動

  • 驗證:希望計算器只能處理正數的運算

@Service
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public void add(int n, int m) {
        System.out.println("日誌:The Method add begin ["+n+","+m+"]");
        int result = n+m;
        System.out.println(
"result = " + result); } @Override public void dev(int n, int m) { System.out.println("日誌:The Method dev begin ["+n+","+m+"]"); int result = n-m; System.out.println("result = " + result); } @Override public void mul(int n, int m) { System.out.println(
"日誌:The Method mul begin ["+n+","+m+"]"); int result = n*m; System.out.println("result = " + result); } @Override public void sub(int n, int m) { System.out.println("日誌:The Method sub begin ["+n+","+m+"]"); int result = n/m; System.out.println("result = " + result); } }

問題

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

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

二、動態代理

代理設計模式的原理使用一個代理將物件包裝起來,然後用該代理物件取代原始物件。任何對原始物件的呼叫都要通過代理。代理物件決定是否以及何時將方法呼叫轉到原始物件上。

2.1、動態代理的實現方式

  • 基於介面實現動態代理: JDK動態代理
  • 基於繼承實現動態代理: Cglib、Javassist動態代理

三、JDK實現動態代理

使用jdk動態代理改進上面的日誌列印

3.1、動態代理概述

Proxy類是動態代理的父類,用於生成代理類或者代理物件的。

//生成代理物件
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

newProxyInstance方法中的三個引數

  • 第一個引數:類載入器物件,動態載入代理類

  • 目標類實現的所有介面,檢視目標物件的方法都來自哪。

  • InvocationHandler介面類的物件

動態代理整個過程是在newProxyInstance方法中的引數InvocationHandler中完成的,動態代理要做的業務邏輯放在InvocationHandler 的invoke方法中編寫。

public Object invoke(Object proxy, Method method, Object[] args)

invoke方法中的三個引數

  • proxy:代理物件,呼叫代理方法會回過頭呼叫invoke方法。

  • method:正在被呼叫的方法物件

  • args:正在被呼叫的方法引數

代理物件呼叫代理方法會回過頭呼叫invoke方法。

3.2、實現動態代理步驟

  • 業務類
@Service
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int n, int m) {
        return n+m;
    }
}
  • 自定義代理類
public class CalculProxy {
    //第一步:目標物件
    private Object target;
    public CalculProxy(Object target) {
        this.target = target;
    }
    //第二步:獲取代理物件

    public Object getObject(Object obj){
       return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
           //第三步:代理物件要做什麼
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String name = method.getName();
                System.out.println("日誌:The Method "+name+" begin "+ Arrays.toString(args));
              ////相當於執行目標類中的方法(add sub 方法)
                Object result = method.invoke(target, args);
                System.out.println(name+":result = " + result);
                return result;
            }
        });
    }
}
  • 測試
@Test
    public void test_method07() {

        //第一步:獲取目標物件
        ArithmeticCalculatorImpl calculService = new ArithmeticCalculatorImpl();
        //第二步:獲取代理物件
        CalculProxy calculProxy = new CalculProxy(calculService);
        ArithmeticCalculator proxy =(ArithmeticCalculator)calculProxy.getObject(calculService);
        proxy.add(1,2);
    }