講一些你所不知道的Java動態代理

簡介

Proxy 是設計模式中的一種。當需要在已存在的 class 上添加或修改功能時,可以通過創建 proxy object 來實現

通常 proxy object 和被代理對象擁有相同的方法,並且擁有被代理對象的引用,可以調用其方法

代理模式應用場景包括

  • 在方法執行前後打印和記錄日誌
  • 認證、參數檢查
  • lazy instantiation (Hibernate, Mybatis)
  • AOP (transaction)
  • mocking

代理有兩種實現方式

  • 靜態代理:在編譯時期,創建代理對象
  • 動態代理:在運行時期,動態創建

對於重複性工作,如打印日誌,靜態代理需要爲每個 class 都創建 proxy class,過程繁瑣和低效,而動態代理通過使用反射在運行時生成 bytecode 的方式來實現,更加方便和強大

過程

因爲 JDK 自帶的 Dynamic proxy 只能夠代理 interfaces,因此被代理對象需要實現一個或多個接口。

先來看一些概念:

  • proxy interface proxy class 實現的接口
  • proxy class 運行時創建的代理 class,並實現一個或多個 proxy interface
  • proxy instance proxy class 的實例
  • InvocationHandler 每個 proxy instance 都有一個關聯的 invocation handler,當調用 proxy 對象的方法時,會統一封裝,並轉發到invoke()方法

InvocationHandler 接口的定義如下

package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}

只定義了一個方法invoke(),參數含義如下

  • Object proxy生成的代理對象
  • Method method調用的方法,類型爲 java.lang.reflect.Method
  • Object[] args調用方法的參數,array of objects

簡單來說就是,調用 proxy object 上的方法,最終都會轉換成對關聯InvocationHandlerinvoke()方法的調用

可以使用java.lang.reflect.Proxy的靜態方法newProxyInstance來創建Proxy object

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
    ...
}

參數說明

  • loader 定義 proxy class 的 ClassLoader
  • interfaces 需要代理的接口
  • h 關聯的 InvocationHandler

例子

使用動態代理打印方法的執行耗時

定義代理接口

public interface Foo {
    String doSomething();
}

實現接口

public class FooImpl implements Foo {
    @Override
    public String doSomething() {
        return "finished";
    }
}

定義 InvocationHandler,target 爲被代理對象的引用,在方法執行完後打印耗時

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimingInvocationHandler implements InvocationHandler {
    private Object target;
    public TimingInvocationHandler(Object target) {
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
        long start = System.nanoTime();
        Object result = method.invoke(target, args);
        long elapsed = System.nanoTime() - start;
        System.out.println(String.format("Executing %s finished in %d ns",
                        method.getName(),
                        elapsed));
        return result;
    }
}

測試

import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
    @Test
        public void test() {
        ClassLoader cl = DynamicProxyTest.class.getClassLoader();
        Class[] interfaces = new Class[]{Foo.class};
        FooImpl fooImpl = new FooImpl();
        InvocationHandler timingInvocationHandler = new TimingInvocationHandler(fooImpl);
        Foo foo = (Foo) Proxy.newProxyInstance(cl, interfaces, timingInvocationHandler);
        foo.doSomething();
    }
}

執行完會打印類似

Executing doSomething finished in 23148 ns

細節

生成 proxy class 的一些屬性和細節

  • public, final, and not abstract.
  • 類名不確定,以 $Proxy 開頭
  • 繼承 java.lang.reflect.Proxy,且 Proxy 實現了 java.io.Serializable 接口,因此 proxy instance 是可以序列化的
  • 按照 Proxy.newProxyInstance() 傳入 interfaces 參數中的接口順序來實現接口
  • 在 proxy class 上調用 getInterfaces,getMethods,getMethod 方法,會返回實現的接口中定義的方法,順序和創建時的參數保持一致
  • 當調用 proxy instance 同名、同 parameter signature 方法時,invoke() 方法的 Method 參數會是最早定義這個方法的 interface 的方法,無論實際調用的方法是什麼
  • 當 Foo 爲實現的代理接口之一時, proxy instanceof Foo 返 true,並且可以轉換 (Foo) proxy
  • Proxy.getInvocationHandler 靜態方法會返回 proxy object 關聯的 invocation handler
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章