Java 動態代理

更多 Java 高級知識方面的文章,請參見文集《Java 高級知識》


代理模式

  • 委托類:具體提供服務
  • 代理類:通過調用委托類對象的方法來提供服務。代理類的作用包括:
    • 調用具體方法前進行預處理,包括:
      • 數據的驗證及過濾
      • 權限的驗證
      • 開啟事務
      • 查詢緩存
      • 建立數據庫連接
      • 記錄日志等等。
    • 調用具體方法后進行后處理,包括:
      • 提交事務
      • 更新緩存
      • 關閉數據庫連接
      • 記錄日志等等。

代理模式分為兩種形式:

  • 靜態代理:程序運行前,代理類已經存在于 class 字節碼文件中,程序運行中只是創建代理類的對象。
  • 動態代理:程序運行中動態創建出代理類及其對象(原理:通過反射來動態產生字節碼)

靜態代理

示例如下:

public class Proxy_Test {
    public static void main(String[] args) throws Exception {
        AddProxy proxy = new AddProxy(new AddImpl());
        proxy.add(1, 2);
    }
}

// 共同的接口
interface Add {
    int add(int a, int b);
}

// 委托類
class AddImpl implements Add {
    public int add(int a, int b) {
        return a + b;
    }
}

// 代理類
class AddProxy implements Add {
    // 代理類關聯一個委托類對象
    private AddImpl impl;

    // 通過構造方法傳入委托類的對象
    public AddProxy(AddImpl impl) {
        this.impl = impl;
    }

    public int add(int a, int b) {
        // 預處理,記錄日志
        System.out.println("Add start");

        // 通過調用委托類對象的方法來提供服務
        return impl.add(a, b);
    }
}

可以看出:

  • 委托類和代理類需要實現同一個接口
  • 委托類具體提供服務
  • 代理類通過調用委托類對象的方法來提供服務,在調用前進行預處理操作,例如記錄日志
  • 代理類關聯一個委托類對象,并通過構造方法傳入委托類的對象

靜態代理的問題

代理類與委托類一一對應。
如果有多個委托類,例如在上述的例子中存在 AddImpl MinusImpl MultiplyImpl DivideImpl,則需要對應地構造多個代理類,產生重復的代碼,因為每個代理類的功能其實都一樣,例如記錄日志。

動態代理

一個代理類完成全部的代理功能,代理一個或多個委托類。

Proxy 類

所在包:import java.lang.reflect.Proxy;
通過 Proxy 類的 newProxyInstance 方法可以為一個或多個接口動態地創建出代理類及其對象。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

該方法返回一個代理類對象,該對象實現了指定的接口 Class<?>[] interfaces。

newProxyInstance 方法中,會調用如下代碼:即在運行時動態生成代理類的字節碼,并將字節碼轉換為代理類的對象。

/*
 * Generate the specified proxy class.
 */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);

InvocationHandler 接口

所在包:import java.lang.reflect.InvocationHandler;
實現如下的 invoke 方法:

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

Processes a method invocation on a proxy instance and returns the result. This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with.
當調用代理類對象的方法時,會自動調用該 invoke 方法。
在該 invoke 方法中進行預處理,調用委托類方法 method,后處理,返回結果。

通過動態代理類實現如上的靜態代理功能,完整的代碼如下:

public class Proxy_Test {
    public static void main(String[] args) throws Exception {
        // 動態創建 AddImpl 的代理類對象
        Add addProxy = (Add) generateProxy(new AddImpl());
        addProxy.add(1, 2);

        // 動態創建 MinusImpl 的代理類對象
        Minus minusProxy = (Minus) generateProxy(new MinusImpl());
        minusProxy.minus(1, 2);
    }

    public static Object generateProxy(Object obj) {
        // 通過 Proxy 類的 newProxyInstance 方法可以為一個或多個接口動態地創建出代理類及其對象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 預處理,記錄日志
                        System.out.println(method.getName() + " start");

                        // 實際調用委托類的具體方法
                        return method.invoke(obj, args);
                    }
                });
    }
}

// 共同的接口
interface Add {
    int add(int a, int b);
}

interface Minus {
    int minus(int a, int b);
}

interface Multiply {
    int multiply(int a, int b);
}

interface Divide {
    int divide(int a, int b);
}

// 委托類
class AddImpl implements Add {
    public int add(int a, int b) {
        return a + b;
    }
}

class MinusImpl implements Minus {
    public int minus(int a, int b) {
        return a - b;
    }
}

class MultiplyImpl implements Multiply {
    public int multiply(int a, int b) {
        return a * b;
    }
}

class DivideImpl implements Divide {
    public int divide(int a, int b) {
        return a / b;
    }
}

動態代理的原理

調用代理類的方法時 addProxy.add(1, 2);,會自動觸發 invoke 方法,invoke 方法中調用委托類的方法 method.invoke(obj, args);。
實際上,invoke 方法不是顯示調用的,它是一個回調函數。

動態代理的問題

委托類需要實現某一個接口,例如 AddImpl 需要實現 AddMinusImpl 需要實現 Minus

CGLib 可以彌補這個缺陷,不需要實現特定的接口。
CGLib 通過繼承來實現,動態產生的代理類實際上為委托類的子類,代理類通過 Method Override 實現了代碼增強。
例如:

Enhancer e = new Enhancer();
e.setSuperclass(AddImpl.class);
e.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 預處理,記錄日志
        System.out.println(method.getName() + " start");

        return method.invoke(o, objects);
    }
});

// 動態產生的代理類實際上為委托類的子類
AddImpl proxy = (AddImpl)e.create();
proxy.add(1, 2);

關于 CGLib 和 JDK Proxy 的比較,可以參見 Stack Overflow

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Java動態代理 引言 最近在看AOP代碼,其中利用到了Java動態代理機制來實現AOP織入。所以好好地把Java...
    草捏子閱讀 1,548評論 0 18
  • 前言 本文是我在學習代理模式時的一篇筆記,除了對代理模式、靜態和動態代理的概念和實現進行了描述外,還有關于動態代理...
  • 相關概念1.1 代理??在某些情況下,我們不希望或是不能直接訪問對象 A,而是通過訪問一個中介對象 B,由 B 去...
    天空在微笑閱讀 440評論 0 0
  • java動態代理(JDK和cglib) JAVA的動態代理 代理模式 代理模式是常用的java設計模式,他的特征是...
    Andy_1777閱讀 466評論 0 0
  • 有些成功的溝通者很嚴肅、有些很幽默、有些很外向、活潑,也有些比較沉默、文靜;這就像世界最美好的音樂有不同種類一樣...
    徐猛_Merlin閱讀 287評論 1 0