講一些你所不知道的Java動態代理
阿新 • • 發佈:2019-05-03
同名 運行 pack 編譯 pri class final art mat 簡介
的
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 interfaceproxy 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 上的方法,最終都會轉換成對關聯InvocationHandler
invoke()
方法的調用
可以使用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
- …
講一些你所不知道的Java動態代理