1. 程式人生 > >Java之動態代理簡介

Java之動態代理簡介

保留 targe tar 再次 靜態方法 idt 代碼生成 handle The

圖截於《大話設計模式》

技術分享圖片

Proxy模式是常用的設計模式,其特征是代理類與委托類有同樣的接口,代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事後處理消息等。

用戶可以更加結構圖,自己編碼完成Proxy模式。這種實現稱為靜態代理。

Java提供了java.lang.reflect.Proxy類與InvocationHandler接口,配合反射,可以實現動態代理。靜態代理的代理類與代理操作,都是事先編碼,運行過程種無法修改代理結構。動態代理的代理與代理操作,都是在運行過程中,動態生成,可以在運行過程中,修改代理結構。

InvocationHandler類提供代理操作行為

,動態構建的代理類使用該接口調用代理操作。

Proxy類主要負責動態構建代理類,有以下靜態方法:

  • InvocationHandler getInvocationHandler(Object proxy)
  • Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
  • boolean isProxyClass(Class<?> cl)
  • Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

調用getProxyClass()會動態生成Proxy類的子類,並使用loader參數指定的類加載器加載;第二個參數interfaces指定該子類將要繼承的接口,可以指定多個接口。

interface Foo {

void funcA();

}

Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);

System.out.println(proxyClass.getName());

for (Class<?> interfaceType : proxyClass.getInterfaces())

System.out.println("\t" + interfaceType);

Class<?> proxyClassB = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class, AutoCloseable.class);

System.out.println(proxyClassB.getName());

for (Class<?> interfaceType : proxyClassB.getInterfaces())

System.out.println("\t" + interfaceType);

輸出如下:

testproxy.$Proxy0

interface testproxy.Foo

testproxy.$Proxy1

interface testproxy.Foo

interface java.lang.AutoCloseable

由輸出可以推測,調用Proxy.getProxyClass()後,生成子類為:

$Proxy* extends Proxy implements interfasces

*為從0開始編號的整數,代表是第幾個被創建的Proxy的子類。interfaces是Proxy.getProxyClass()方法的第二個不定參數。這些類的字節碼創建在內存中。例如上面代碼生成的子類為:

$Proxy0 extends Proxy implements Foo {...}

$Proxy1 extends Proxy implements Foo, AutoCloseable {...}

實現同接口的Proxy子類,只會被創建一次,擁有共同的Class實例。

Class<?> proxyClassA = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);

Class<?> proxyClassB = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);

System.out.println(proxyClassA.getName());

System.out.println(proxyClassB.getName());

輸出如下:

testproxy.$Proxy0

testproxy.$Proxy0

由於Proxy類只有一個帶InvocationHandler接口的參數,所有所以需要獲取指定版本的構造函數後,傳入InvocationHandler接口的實現類獲取動態子類的實例。

Foo fooProxy = (Foo) proxyClass.getConstructor(InvocationHandler.class)

.newInstance(handler);

Proxy類提供的Proxy.newProxyInstance(),一步到位,簡化了獲取動態子類實例的操作。

Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),

new Class<?>[] { Foo.class },

handler)

fooProxy的實例為"$Proxy0 extends Proxy implements Foo {...}"類

InvocationHandler接口有一個invoke()方法需要實現,動態代理類正式調用這個方法執行代理操作。舉個例子:

class SimpleHandler implements InvocationHandler {

final private Object target;

public SimpleHandler(Object target) {

// *需要保留委托類的引用

this.target = target;

}

@Override

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

System.out.println("---Proxy before---");

// *不適用第一個參數proxy,使用構造函數保留的引用調用委托類方法

Object result = method.invoke(target, args);

System.out.println("---Proxy end---");

return result;

}

}

使用以下委托類和代碼測試。

class FooImp implements Foo {

@Override

public void funcA() {

System.out.println("FooImp");

}

}

// Test

Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),

new Class<?>[] { Foo.class },

new SimpleHandler(new FooImp()));

fooProxy.funcA();

System.out.println(fooProxy.toString());

輸出如下:

---Proxy before---

FooImp

---Proxy end---

---Proxy before---

---Proxy end---

testproxy.FooImp@4aa298b7

fooProxy的是實例是Proxy類的動態子類,調用該子類的funcA()方法時,實際上先調用了invoke()方法,再通過反射調用委托類的funcA()方法。

為什麽toString()方法也被代理了?

實際上,Object的equal()、hashCode()也同時被代理的,這個與Proxy子類的構建過程有關,可以參考資料《JDK動態代理實現原理》。

實現InvocationHandler接口時,紅色的註釋指出兩個疑點:

  • 為什麽要保留委托類實例的引用?
  • 為什麽不使用第一個參數proxy?

這個兩個問題其實是一個問題,invoke()函數的第一個參數proxy引用的實例是Proxy類的動態子類,而不是委托類。如果使用反射調用動態子類的方法,又會再次調用invoke()函數,陷入無限循環中,直到內存移除崩潰。因此,需要保留委托類的引用,讓invoke()方法可以調用到委托類的方法。這樣又引出一個問題,proxy參數又什麽用?

proxy參數可以做為返回值,實現方法鏈。具體可以參考《Understanding "proxy" arguments of the invoke method of java.lang.reflect.InvocationHandler

Java之動態代理簡介