Java JDK動態代理Proxy類的原理是什麼?
- 什麼是代理?
先從代理開始講。
代理這種設計模式其實很好理解,基本就是最簡單的一個 “組合”。比如說下面這個例子,我們有 A 這個類,本來可以直接呼叫 A 類的 foo() 方法。但代理模式就非要把 A 類當成 B 類的一個成員欄位放在 B 類裡面。然後因為 A 類和 B 類都實現了 Interface 這個介面,所以 B 類裡也有 foo()方法。而且 B 類裡的 foo()方法就是傻瓜式的呼叫 A 類的 foo()方法。
interface Interface{public void foo();}
/**委託類*/
class A implements Interface{
public void foo(){System.out.println("Method a of class A!");}
}
/**代理類*/
class B implements Interface{
public A a=new A();
public void foo(){a.foo();}
}
/**使用者*/
class Consumer{
public static void consum(Interface i){
i.foo();
}
}
/**測試*/
public class TestProxy{
public static void main (String[] args){
Interface i=new B();
Consumer.consum(i);
}
}
- 代理有什麼好處?
乍一看,代理方法完全是多此一舉,B 類的行為和 A 類完全一樣,沒有任何意義。但實際上,B 類的 foo() 方法在直接呼叫 A 類 foo() 方法之前和之後,可以做很多事情。舉個例子,如果在 B 類里加個靜態計數字段 count,然後每次呼叫 foo() 方法之後都計一下數,就可以監控 A 類 foo() 方法被呼叫的次數。
class B implements Interface{
public static long count=0;
public A a=new A();
public void foo(){a.foo();count++;}
}
所以代理類裡能非常好地控制,輔助被代理類,甚至是增加額外的功能。而且一般來說代理類 B 和被代理 A 都會實現同樣的介面,這樣對使用者端(就是上面例子裡的 Consumer 類)的程式碼沒有任何影響,耦合很低。
- 什麼是動態代理?
上面例子裡在 A 類外面套一個 B 類好像很簡單,但實際到了工程級別的程式碼,需要代理的就不止一個兩個了。每個代理類都手動寫會累死,而且很枯燥,是沒有技術含量的重複。所以這個時候 Java 的反射功能就立功了。代理類 B 是可以完全用反射動態生成的。
怎麼動態生成 B 類呢?下面有個例子,通過反射動態載入 B 類,然後呼叫 B 類的 foo() 方法。
public class TestDynamicProxy{
public static void main(String[] args){
try{
Class<?> refB=B.class;
Method refFoo=refB.getDeclaredMethod("foo");
Object refObj=refB.newInstance();
refFoo.invoke(refObj);
}catch(Exception e){
System.out.println(e);
}
}
}
- B.class 獲得了 B 類的 Class 物件。
- Class#getDeclaredMethod() 方法根據方法的名稱 "foo" 獲得了 foo() 方法的 Method 物件。
- 最後呼叫這個 Method 物件的 invoke() 來執行這個方法。
但這個是動態生成嗎?**明顯不是!**上面這個方法只是在 B 類已經寫好了的情況下動態呼叫 B 類。其實並沒有動態生成 B 類,根本不能叫動態生成。真的要完全憑空用反射 “寫” 一個 B 類的位元組碼檔案出來然後載入它,其實要複雜地多,這就是為什麼需要 Proxy 工具來替我們做的原因。
4.Proxy 類怎麼實現動態代理?
Proxy 類裡能替我們生成一個代理類物件的,就是 newProxyInstance()
方法。現在回過頭看它的三個引數,
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- 第一個 ClassLoader 是為了生成 B 類的 Class 物件。作用是根據一組類的位元組碼 byte[] 直接生成這個類的 Class 物件。
- 第二個引數是由委託類實現的介面的 Class 物件陣列。主要是包含了最重要的代理類需要實現的介面方法的資訊。
- 最後一個最重要的就是一個實現了 InvocationHandler 介面的物件。InvocationHandler 介面在 java.lang.reflect 包裡。最主要的就是定義了 invoke( ) 方法。它就是假設在已經動態生成了最後的 proxy 代理物件,以及所有介面定義的方法 Method 物件以及方法的引數的情況下,定義我們要怎麼呼叫這些方法的地方。
這三個引數的分工用大白話講就是:第一引數 ClassLoader 和第二引數介面的 Class 物件是用來動態生成委託類的包括類名,方法名,繼承關係在內的一個空殼。用 B 類的例子演示就像下面這樣,
class $ProxyN implements Interface{
public void foo(){
//do something...
}
}
只有介面定義的方法名,沒有實際操作。實際的操作是交給第三個引數 InvocationHandler
的 invoke()
方法來執行。
所以最主要的業務邏輯應該是在第三個引數 InvocationHandler
的 invoke()
方法裡定義。下面程式碼,是根據之前 A 類 B 類的例子用 Proxy 類實現動態代理的 Demo。程式碼裡原先的 B 類被擦掉了,完全由 Proxy 類動態生成。
interface Interface{public void foo();}
class A implements Interface{
public void foo(){System.out.println("Method a of class A!");}
}
/* //這是Proxy要動態生成的B類。
class B implements Interface{
public void foo(){a.foo();}
public A a=new A();
}
*/
class Consumer{
public static void consum(Interface i){
i.foo();
}
}
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try{
return method.invoke(proxied, args);
}catch(Exception e){
System.out.println(e);
return null;
}
}
}
public class TestDynamicProxy{
public static void main(String[] args){
A a=new A();
//直接把A類物件a當引數傳進去,就動態產生一個代理類物件
Interface proxy = (Interface)Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class<?>[]{Interface.class }, new DynamicProxyHandler(a));
Consumer.consum(proxy);
}
}
在實現了 InvocationHandler 介面的 DynamicProxyHandler 類裡有一個被代理類的物件 proxied 作為成員欄位。在獲得了引數傳進來的代理類物件和 Method 物件之後,直接用 Method#invoke(Object o) 方法,呼叫了代理類物件的方法。第一個引數 ClassLoader 直接用的是 Interface 介面的類載入器 (Interface.class.getClassLoader())。第二引數就是 Interface 介面的 Class 物件。
然後,剩下的事就交給 Proxy 來完成。關鍵的難點在於怎麼根據給定的 ClassLoader 和介面的方法資訊動態生成一個所謂 “空殼”。其實 newProxyInstance() 方法隱藏了非常多其他的複雜性,比如說訪問許可權檢查,包路徑設定,安全檢查等等瑣碎的事,但這裡先不說。只說核心步驟。
下面擷取 newProxyInstance() 方法原始碼裡比較重要的一段貼上來,看看底層是怎麼實現的。
/*
* Choose a name for the proxy class to generate.
*/
long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
可以看到,proxyName 是動態生成的代理類的名稱,一般是 ·$ProxyN 的格式。N 代表代理是 N 次生成動態代理。
然後見證奇蹟的時刻到了,關鍵的核心步驟有兩個:
ProxyGenerator.generateProxyClass()
方法生成了類載入器要用到的位元組碼。它需要的引數只有兩個,1)類名,2)實現的介面的 Class 物件。然後它就神奇地生成了一堆位元組碼 byte[],基本就是一個憑空造出來的編譯好的. class 檔案。這個方法來自神祕的 sun.misc 包。也查不到原始碼。- 最後神祕的位元組碼和載入器,以及類名一起被交到另一個 native 方法 defineClass0( ) 裡,由它生成代理類的 Class 物件。至於 native 方法怎麼實現,原始碼裡也查不到。
最後再總結一下,使用 Proxy 的三步,
- 在第三個引數,實現 InvocationHandler 介面的物件的 invoke() 方法裡寫業務邏輯。
- 第二個引數是代理實現介面的 Class 物件
- 第一個引數是一個 ClassLoader。一般直接用呼叫類的載入器
如果實在想知道鬼畜的 ProxyGenerator.generateProxyClass()的內部原理,就看誰能把人肉原始碼機 R 大 @RednaxelaFX 召喚出來了,哈哈:v