黑馬程式設計師 動態代理
一,什麼是動態代理?
動態代理就是使用反射動態的實現一個類的載入從而實現動態例項化一個類的物件的效果。
二,怎樣實現動態代理?
1,首先讓代理類實現InvocationHandler介面,此介面中的invoke方法如下:
Object invoke(Object proxy,
Method method,
Object[] args)
throws Throwable在代理例項上處理方法呼叫並返回結果。在與方法關聯的代理例項上呼叫方法時,將在呼叫處理程式上呼叫此方法。
引數:
proxy - 在其上呼叫方法的代理例項
method - 對應於在代理例項上呼叫的介面方法的 Method 例項。Method 物件的宣告類將是在其中宣告方法的介面,該介面可以是代理類賴以繼承方法的代理介面的超介面。
args - 包含傳入代理例項上方法呼叫的引數值的物件陣列,如果介面方法不使用引數,則為 null。基本型別的引數被包裝在適當基本包裝器類(如 java.lang.Integer 或 java.lang.Boolean)的例項中。
返回:
從代理例項的方法呼叫返回的值。如果介面方法的宣告返回型別是基本型別,則此方法返回的值一定是相應基本包裝物件類的例項;否則,它一定是可分配到宣告返回型別的型別。如果此方法返回的值為 null
並且介面方法的返回型別是基本型別,則代理例項上的方法呼叫將丟擲 NullPointerException。否則,如果此方法返回的值與上述介面方法的宣告返回型別不相容,則代理例項上的方法呼叫將丟擲 ClassCastException。
丟擲:
Throwable - 從代理例項上的方法呼叫丟擲的異常。該異常的型別必須可以分配到在介面方法的 throws 子句中宣告的任一異常型別或未經檢查的異常型別 java.lang.RuntimeException 或 java.lang.Error。如果此方法拋
出經過檢查的異常,該異常不可分配到在介面方法的 throws 子句中宣告的任一異常型別,代理例項的方法呼叫將丟擲包含此方法曾丟擲的異常的
2,其次是在代理類中要有Proxy類的支援,Proxy是專門用於完成代理的代理操作類,可以通過此類為一個或多個介面動態地生成實現類。proxy類提供的方法如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException返回一個指定介面的代理類例項,該介面可以將方法呼叫指派到指定的呼叫處理程式。此方法相當於:
Proxy.getProxyClass(loader, interfaces).
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });
Proxy.newProxyInstance 丟擲 IllegalArgumentException,原因與 Proxy.getProxyClass 相同。
引數:
loader - 定義代理類的類載入器
interfaces - 代理類要實現的介面列表
h - 指派方法呼叫的呼叫處理程式
返回:
一個帶有代理類的指定呼叫處理程式的代理例項,它由指定的類載入器定義,並實現指定的介面
丟擲:
IllegalArgumentException - 如果違反傳遞到 getProxyClass 的引數上的任何限制
NullPointerException - 如果 interfaces 陣列引數或其任何元素為 null,或如果呼叫處理程式 h 為 null
Proxy 提供用於建立動態代理類和例項的靜態方法,它還是由這些方法建立的所有動態代理類的超類。
建立某一介面 Foo 的代理:
InvocationHandler handler = new MyInvocationHandler(...);
Class proxyClass = Proxy.getProxyClass(
Foo.class.getClassLoader(), new Class[] { Foo.class });
Foo f = (Foo) proxyClass.
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });
或使用以下更簡單的方法:
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[] { Foo.class },
handler);
動態代理類(以下簡稱為代理類)是一個實現在建立類時在執行時指定的介面列表的類,該類具有下面描述的行為。 代理介面 是代理類實現的一個介面。 代理例項 是代理類的一個例項。 每個代理例項都有一個關聯的呼叫處理程式 物件,它可以實現介面 InvocationHandler。通過其中一個代理介面的代理例項上的方法呼叫將被指派到例項的呼叫處理程式的 Invoke 方法,並傳遞代理例項、識別呼叫方法的 java.lang.reflect.Method 物件以及包含引數的 Object 型別的陣列。呼叫處理程式以適當的方式處理編碼的方法呼叫,並且它返回的結果將作為代理例項上方法呼叫的結果返回。
代理類具用以下屬性:
代理類是公共的、最終的,而不是抽象的。
未指定代理類的非限定名稱。但是,以字串 "$Proxy" 開頭的類名空間應該為代理類保留。
代理類擴充套件 java.lang.reflect.Proxy。
代理類會按同一順序準確地實現其建立時指定的介面。
如果代理類實現了非公共介面,那麼它將在與該介面相同的包中定義。否則,代理類的包也是未指定的。注意,包密封將不阻止代理類在執行時在特定包中的成功定義,也不會阻止相同類載入器和帶有特定簽名的包所定義的類。
由於代理類將實現所有在其建立時指定的介面,所以對其 Class 物件呼叫 getInterfaces 將返回一個包含相同介面列表的陣列(按其建立時指定的順序),對其 Class 物件呼叫 getMethods 將返回一個包括這些介面中所有方法的 Method 物件的陣列,並且呼叫 getMethod 將會在代理介面中找到期望的一些方法。
如果 Proxy.isProxyClass 方法傳遞代理類(由 Proxy.getProxyClass 返回的類,或由 Proxy.newProxyInstance 返回的物件的類),則該方法返回 true,否則返回 false。
代理類的 java.security.ProtectionDomain 與由引導類載入器(如 java.lang.Object)載入的系統類相同,原因是代理類的程式碼由受信任的系統程式碼生成。此保護域通常被授予 java.security.AllPermission。
每個代理類都有一個可以帶一個引數(介面 InvocationHandler 的實現)的公共構造方法,用於設定代理例項的呼叫處理程式。並非必須使用反射 API 才能訪問公共構造方法,通過呼叫 Proxy.newInstance 方法(將呼叫 Proxy.getProxyClass 的操作和呼叫帶有呼叫處理程式的構造方法結合在一起)也可以建立代理例項。
代理例項具有以下屬性:
提供代理例項 proxy 和一個由其代理類 Foo 實現的介面,以下表達式將返回 true:
proxy instanceof Foo
並且以下的強制轉換操作將會成功(而不丟擲 ClassCastException):
(Foo) proxy
每個代理例項都有一個關聯的呼叫處理程式,它會被傳遞到其構造方法中。靜態 Proxy.getInvocationHandler 方法將返回與作為其引數傳遞的代理例項相關的呼叫處理程式。
代理例項上的介面方法呼叫將按照該方法的文件描述進行編碼,並被指派到呼叫處理程式的 Invoke 方法。
在代理例項上的 java.lang.Object 中宣告的 hashCode、equals 或 toString 方法的呼叫將按照與編碼和指派介面方法呼叫相同的方式進行編碼,並被指派到呼叫處理程式的 invoke 方法,如上所述。傳遞到 invoke 的 Method 物件的宣告類是 java.lang.Object。代理類不重寫從 java.lang.Object 繼承的代理例項的其他公共方法,所以這些方法的呼叫行為與其對 java.lang.Object 例項的操作一樣。
在多代理介面中重複的方法
當代理類的兩個或多個介面包含一個具有相同名稱和引數簽名的方法時,代理類的介面順序變得非常重要。在代理例項上呼叫重複方法 時,傳遞到呼叫處理程式的 Method 物件沒有必要成為其宣告類可以從介面(通過該介面呼叫代理方法)的引用型別指派的物件。此限制存在的原因是,生成的代理類中的相應方法實現無法確定它通過哪一個介面呼叫。因此,在代理例項上呼叫重複方法時,第一個介面中的方法的 Method 物件包含介面的代理類列表中的方法(直接或通過超級介面繼承),該物件會傳遞到呼叫處理程式的 invoke 方法,無論該方法呼叫通過哪一種引用型別發生。
如果代理介面包含某一方法,它的名稱和引數簽名與 java.lang.Object 的 hashCode、equals 或 toString 方法相同,那麼在代理例項上呼叫這樣的方法時,傳遞到呼叫處理程式的 Method 物件將使 java.lang.Object 成為其宣告類。換句話說,java.lang.Object 公共的非最終方法理論上在所有代理介面之前,以便確定哪一個 Method 物件傳遞到呼叫處理程式。
還要注意,當重複方法被指派到呼叫處理程式時,invoke 方法只可以丟擲經過檢查的異常型別,該異常型別可以使用所有 代理介面(可以通過它呼叫)中方法的 throws 子句指派一種異常型別。如果 invoke 方法丟擲一個經過檢查的異常,該異常沒有指派給任何由一個代理介面(可以通過它呼叫)中的方法宣告的異常型別,那麼該代理例項上的呼叫將丟擲一個未經檢查的 UndeclaredThrowableException。此限制表示並非所有的由傳遞到 invoke 方法的 Method 物件上呼叫 getExceptionTypes 返回的異常型別都可以由 invoke 方法成功丟擲。
下面是動態代理的一個舉例說明:
1,定義MyInvocationHandler類
package proxy.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandler implements InvocationHandler{
private Object obj;//真實主題
public Object bind(Object obj){//繫結真實主題操作
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),this);//取得代理類
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)//動態呼叫方法
throws Throwable {
// TODO Auto-generated method stub
Object temp = method.invoke(this.obj, args);//呼叫方法,傳入真實主題和引數
return temp;//返回方法的返回資訊
}
}
2,定義介面
package proxy.test;
public interface Subject { //定義Subject介面
public String print(String word);//定義抽象方法print
}
3,定義真實主題實現類
package proxy.test;
public class RealSubject implements Subject{
@Override
public String print(String word) {
// TODO Auto-generated method stub
return "內容:"+word;
}
}
4,測試動態代理
package proxy.test;
public class DynaProxyDemo {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
MyInvocationHandler handler = new MyInvocationHandler();
Subject sub = (Subject)handler.bind(new RealSubject());
String content = sub.print("hello");
System.out.println(content);
}
}