查看doc文檔就可以知道,在java.lang.reflect包中有一個叫Proxy的類。下面是doc文檔對Proxy類的說明:
"A dynamic proxy class (simply referred to as a proxy class below) is a class that implements a list of interfaces specified at runtime when the class is created, with behavior as described below. A proxy interface is such an interface that is implemented by a proxy class. A proxy instance is an instance of a proxy class. Each proxy instance has an associated invocation handler object, which implements the interface InvocationHandler
."
Proxy類的設計用到代理模式的設計思想,Proxy類對象實現了代理目標的所有接口,并代替目標對象進行實際的操作。但這種替代不是一種簡單的替代,這樣沒有任何意義,代理的目的是在目標對象方法的基礎上作增強,這種增強的本質通常就是對目標對象的方法進行攔截。所以,Proxy應該包括一個方法攔截器,來指示當攔截到方法調用時作何種處理。InvocationHandler就是攔截器的接口。
InvocationHandler接口也是在java.lang.reflec
Object invoke(Object proxy, Method method, Object[] args)
這個接口有三個參數,其中第二和第三個參數都比較好理解,一個是被攔截的方法,一個是該方法的參數列表。關鍵是第一個參數。按照doc文檔的解析,
proxy - the proxy instance that the method was invoked on
也就是說,proxy應該是一個代理實例,但為什么要傳入這個參數呢?
帶著這個問題,自己編了個小程序作了一點試驗。
///////////////////////////////////////
public interface IAnimal {
void info();
}
////////////////////////////////////
public class Dog implements IAnimal
{
public void info() {
System.out.println("I am a dog!");
}
}
///////////////////////////////////////
import java.lang.reflect.*;
public class ProxyTest {
public static void main(String[] args) throws InterruptedException {
final IAnimal animal = new Dog();
Object proxyObj =Proxy.newProxyInstance(
animal.getClass().getClassLoader(),
animal.getClass().getInterfaces(),
new InvocationHandler()
{
public Object invoke(Object proxy, Method method, Object[] args)
{
try {
System.out.println("被攔截的方法:" + method.getName());
return method.invoke(animal, args);
}
catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
});
if(proxyObj instanceof IAnimal)
{
System.out.println("the proxyObj is an animal!");
}
else
{
System.out.println("the proxyObj isn't an animal!");
}
if(proxyObj instanceof Dog)
{
System.out.println("the proxyObj is a dog!");
}
else
{
System.out.println("the proxyObj isn't a dog!");
}
IAnimal animalProxy = (IAnimal)proxyObj;
animalProxy.info();
animalProxy.hashCode();
System.out.println(animalProxy.getClass().getName().toString());
}
}
程序執行的結果如下:
the proxyObj is an animal!
the proxyObj isn't a dog!
被攔截的方法:info
I am a dog!
被攔截的方法:hashCode
$Proxy0
從結果可以看出以下幾點:
1. proxyObj 是一個實現了目標對象接口的對象,而不同于目標對象。也就是說,這種代理機制是面向接口,而不是面向類的。
2. info方法(在接口中)被成功攔截了,hashCode方法也成功被攔截了,但意外的是,getClass方法(繼承自Object 類的方法)并沒有被攔截!!
3. 應用調試還可以看出Invocation接口中invoke方法的傳入的proxy參數確實就是代理對象實例proxyObj
為何getClass()沒有被攔截?proxy參數又有何用呢?
先不管,做一個試驗看看。既然這個proxy參數就是代理實例對象,它理所當然和proxyObj是一樣的,可以調用info等方法。于是我們可以在invoke方法中加上如下一條語句:
((IAnimal)proxy).info();
結果是:
the proxyObj is an animal!
the proxyObj isn't a dog!
被攔截的方法:info
被攔截的方法:info
.......
被攔截的方法:info
被攔截的方法:info
然后就是棧溢出
結果是很明顯的,在invoke方法中調用proxy中的方法會再一次引發invoke方法,這就陷入了死循環,最終結果當然是棧溢出的。
可以在invoke方法中調用proxy.getClass(), 程序可以正常運行。但如果調用hashCode()方法同樣會導致棧溢出。
通過上面的試驗,可以得出一些初步結論,invoke 接口中的proxy參數不能用于調用所實現接口的方法。奇怪的是hashCode()和getClass()方法都是從Object中繼承下來的方法,為什么一個可以另一個不可以呢?帶首疑問到doc文檔看一下Object中這兩個方法,發現getClass()是定義為final的,而hashCode()不是。難道是這個原因,于是找到一個非final方法,如equals試了一下,真的又會導致棧溢出;找另一個final方法如wait(),試了一下,invoke又不攔截了。final 難道就是關鍵之處?
還有一個問題就是proxy有什么用?既然proxy可以調用getClass()方法,我們就可以得到proxy的Class類象,從而可以獲得關于proxy代理實例的所有類信息,如方法列表,Annotation等,這就為我們提供的一個分析proxy的有力工具,如通過分析Annotation分析方法的聲明式事務需求。我想傳入proxy參數應該是這樣一個用意吧。
InvocationHandler的資料
屬于包:java.lang.reflect
-
public interface InvocationHandler
InvocationHandler
是代理實例的調用處理程序 實現的接口。
每個代碼實例都具有一個關聯的調用處理程序。對代理實例調用方法時,將對方法調用進行編碼并將其指派到它的調用處理程序的 invoke
方法。
- 從以下版本開始:
- JDK1.3
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
子句中聲明的任一異常類型,代理實例的方法調用將拋出包含此方法曾拋出的異常的UndeclaredThrowableException
。