運行時(shí)類(lèi)型信息(RunTime Type Information,RTTI)使得你在程序運行時(shí)發(fā)現和使用類(lèi)型
信息。RTTI主要用來(lái)運行時(shí)獲取向上轉型之后的對象到底是什么具體的類(lèi)型。
1.Class對象:
JAVA使用Class對象來(lái)執行RTTI。每個(gè)類(lèi)都有一個(gè)Class對象,它用來(lái)創(chuàng )建這個(gè)類(lèi)的所有
對象,反過(guò)來(lái)說(shuō),每個(gè)類(lèi)的所有對象都會(huì )關(guān)聯(lián)同一個(gè)Class對象(對于數組來(lái)說(shuō),維數、類(lèi)
型一致的數組的Class 對象才是相同的),每個(gè)對象的創(chuàng )建都依賴(lài)于Class對象的是否創(chuàng )建,
Class對象的創(chuàng )建發(fā)生在類(lèi)加載(java.lang.ClassLoader)的時(shí)候。
java.lang.Class類(lèi)實(shí)現了Serializable、GenericDeclaration、Type、AnnotatedElement四個(gè)接口,
分別實(shí)現了可序列化、泛型定義、類(lèi)型、元數據(注解)的功能。
你可以把 Class對象理解為一個(gè)類(lèi)在內存中的接口代理(它代理了這個(gè)類(lèi)的類(lèi)型信息、方法
簽名、屬性),JVM加載一個(gè)類(lèi)的時(shí)候首先創(chuàng )建Class對象,然后創(chuàng )建這個(gè)類(lèi)的每個(gè)實(shí)例的
時(shí)候都使用這個(gè)Class 對象。
Class只有一個(gè)私有的無(wú)參構造方法,也就是說(shuō)Class的對象創(chuàng )建只有JVM可以完成。
如何驗證同一個(gè)類(lèi)的多個(gè)對象的Class對象是一個(gè)呢?
Cf1 cf1 = new Cf1();
Class clazz = Cf1.class;
System.out.println(cf1.getClass() == clazz);
我們知道==用來(lái)比較引用是否相等(也就是同一個(gè)引用),上面的輸出語(yǔ)句結果是true。那
么Class對象是否相等是JAVA對象中唯一可以使用==判斷的。
如何獲取 Class對象:
1.所有的引用數據類(lèi)型(類(lèi)-類(lèi)型)的類(lèi)名、基本數據類(lèi)型都可以通過(guò).class方式獲取其Class
對象(對于基本數據類(lèi)型的封裝類(lèi)還可以通過(guò).TYPE 的方式獲取其Class 對象,但要注
意.TYPE 實(shí)際上獲取的封裝類(lèi)對應的基本類(lèi)型的Class 對象的引用,那么你可以判斷出
int.class==Integer.TYPE 返回true,int.class==Integer.class 返回false?。?,通過(guò)這種方式
不會(huì )初始化靜態(tài)域,使用.class、.TYPE 的方式獲取Class對象叫做類(lèi)的字面常量;
2.Class 的forName(String name)傳入一個(gè)類(lèi)的完整類(lèi)路徑也可以獲得Class 對象,但由于使
用的是字符串,必須強制轉換才可以獲取泛型的Class<T>的Class對象,并且你必須獲取這
個(gè)方法可能拋出的ClassNotFoundException異常。
2.對于引用數據類(lèi)的引用(必須初始化),可以通過(guò)Object 類(lèi)繼承的getClass()方法獲取這
個(gè)引用的Class對象,由于引用已經(jīng)被初始化,所以這種方式也不會(huì )初始化靜態(tài)域,因為靜
態(tài)域已經(jīng)被初始化過(guò)。另外,前面兩種方式如果說(shuō)是創(chuàng )建Class對象,那么這種方式應該是
取得Class對象,因為類(lèi)的實(shí)例已經(jīng)被創(chuàng )建,那么Class對象也一定早就被創(chuàng )建。
Class的常用方法:
l forName(String name):這是一個(gè)靜態(tài)方法,傳入的參數是一個(gè)類(lèi)的完整類(lèi)路徑的
字符串,返回這個(gè)類(lèi)的Class 對象,前面說(shuō)過(guò)Class 對象的創(chuàng )建發(fā)生在類(lèi)的加載時(shí),所
以這個(gè)方法會(huì )導致靜態(tài)成員被調用;
l forName(String name,boolean initialize,ClassLoader loader):這是上面的方
法的重載方法,initialize指定在創(chuàng )建Class對象時(shí)是否初始化這個(gè)類(lèi)(即是否執行靜態(tài)成
員,由于在一次JVM的執行中,靜態(tài)成員的初始化只類(lèi)加載的時(shí)候執行一次,所以如果
之前這個(gè)類(lèi)已經(jīng)被加載,那么即使initialize為true也不會(huì )再次執行靜態(tài)成員的加載),
loader指定使用哪個(gè)類(lèi)加載器的實(shí)現類(lèi)
(Thread.currentThread().getContextClassLoader()可以獲取當前線(xiàn)程使用的類(lèi)
加載器)。forName(***)方法不可以獲取基本數據類(lèi)型的Class對象。
如果要測試initialize是否起作用,請不要在main()方法測試自身類(lèi),因為main()是靜態(tài)方法,
執行這個(gè)方法會(huì )導致靜態(tài)域被初始化,所以你的initialize無(wú)論是true還是false,效果都是一樣
的。
l asSubClass(Class superClass):這個(gè)方法是將父類(lèi)的class 對象作為參數傳入,并
將其強制轉換成當前的Class對象(子類(lèi)的Class對象)。
例:
Class<List> clazz = List.class;
Class<? extends List> subClazz = ArrayList.class.asSubclass(clazz);
System.out.println(subClazz.getCanonicalName());
注意紅色的部分不能寫(xiě)成Class<ArrayList>形式,因為asSubclass()只知道是要轉換成子
類(lèi)的Class對象,但不知道是哪個(gè)子類(lèi)。
l cast(Object o):這個(gè)方法是將傳入的對象強制轉換成Class 對象所代表的類(lèi)型的對
象;
l getClassLoader():返回創(chuàng )建當前Class 對象的類(lèi)加載器,默認的,應用程序獲得的
是sun.misc.Launcher$AppClassLoader , Applet 程序獲得的是
sun.applet.AppletClassLoader;
l getCanonicalName():返回JAVA 語(yǔ)言中所定義的底層類(lèi)的規范化名稱(chēng),如果沒(méi)有
規范化名稱(chēng)就返回null,對于普通的引用數據類(lèi)型,這個(gè)方法和getName()方法都返回
完整的類(lèi)路徑,對于數組(以字符串數組為例),getName()返回[Ljava.lang.String;,
這個(gè)方法返回java.lang.String[];
l getSimpleName():返回底層規范化名稱(chēng)的簡(jiǎn)寫(xiě),也就是去掉包名;
l getConstructors():返回Class 對象的公有構造器的java.lang.reflect.Contructor 對象
數組,如果找不到匹配的構造方法,返回NoSuchMethodExcetion異常;
l getConstructor(Class<?> …parameterTypes):按照指定的可變參數列表,返回
符合參數條件的公有構造方法的Constructor,這里不可能是數組,因為構造方法不可能
重復,如果找不到匹配的構造方法,返回NoSuchMethodExcetion異常;
l getDeclaredConstructors():返回Class 對象的所有構造器的Constructor 對象,如
果找不到匹配的構造方法,返回NoSuchMethodExcetion異常;
l getDeclaredConstructor(Class<?> …parameterTypes):按照指定的可變參數
列表,返回符合參數條件的所有構造方法的Constructor,這里不可能是數組,因為構造
方法不可能重復,如果找不到匹配的構造方法,返回NoSuchMethodExcetion異常;
l getFields():獲取Class對象的所有公有成員屬性的java.lang.reflect.Field數組;
l getField(String name):按照字段名稱(chēng)獲取公有字段的Field對象,注意name是區
分大小寫(xiě)的;
l getDeclaredFields():獲取Class對象的所有成員屬性的Field數組;
l getDeclaredField(String name):按照字段名稱(chēng)獲取所有字段的Field 對象,注意
name是區分大小寫(xiě)的;
l getMethods():獲取Class 對象的公有方法(以及從父類(lèi)繼承的方法,但不包含構造
方法)的java.lang.reflect.Method數組;
l getMethod(String name,Class<?> …parameterTypes):按照name 指定的方法
名稱(chēng),parameterTypes 指定的可變數組獲取公有方法(以及從父類(lèi)繼承的方法,但不包
含構造方法)的Method對象,注意name 是區分大小寫(xiě)的;
l getDeclaredMethods():獲取Class 對象的所有方法(不包含父類(lèi)繼承的方法,構造
方法)的Method數組;
l getDeclaredMethod(String name,Class<?> …parameterTypes):按照name
指定的方法名稱(chēng),parameterTypes 指定的可變數組獲取所有方法(不包含父類(lèi)繼承的方
法,構造方法)的Method對象,注意name 是區分大小寫(xiě)的;
l getGenericInterface():以Type 數組的形式返回Class 對象的類(lèi)直接實(shí)現的包含泛
型參數的接口,返回順序是implements后的接口順序;
l getInterface():以Class 數組的形式返回Class對象的類(lèi)直接實(shí)現的接口,返回順序
是implements后的接口順序;
l getModifiers():以java.lang.reflect.Modifier 的常量值的形式返回Class對象的類(lèi)的修
飾的整型值的和;
l getGenericSuperclass():以Type數組的形式返回Class對象的類(lèi)直接實(shí)現的包含泛
型參數的超類(lèi), 返回順序是extends后的接口順序;
l getSuperclass():以Class 數組的形式返回Class對象的類(lèi)直接實(shí)現的超類(lèi), 返回順序
是extends后的接口順序;
l getName():以String 形式返回Class 對象所表示的實(shí)體的完整類(lèi)名,基本數據類(lèi)型
返回自身,數組類(lèi)型(以String 數組為例)返回[Ljava.lang.String;,這個(gè)方法沒(méi)有
getCanonicalName()返回的完整,但不是所有的類(lèi)型都有底層的規范化名稱(chēng);
l getPackage():以java.lang.reflect.Package形式返回Class對象的類(lèi)所在的包,基本數
據類(lèi)型、數組拋出異常;
l getResource(String url):這個(gè)方法使用當前Class對象的ClassLoader 實(shí)體加載url
指定的資源,返回java.net.URL 對象。如果url 以\ 開(kāi)頭,那么就從當前的classPath
開(kāi)始定位資源,否則就從當前Class 對象的類(lèi)所在的包開(kāi)始定位資源,Hibernate 要求
*.hbm.xml 必須和PO 類(lèi)在一個(gè)包下,就是利用了沒(méi)有\ 開(kāi)頭的url 傳入這個(gè)方法實(shí)現
的;
l getResourceAsStream(String url):這個(gè)方法和上面的方法一樣,只不過(guò)返回的是
java.io.InputStream的對象;
l isAssignableFrom(Class<?> class):判斷當前Class對象的類(lèi)是否是參數class的
類(lèi)的超類(lèi)或者接口;
l isInstance(Object o):判斷參數o是否是Class 對象的類(lèi)的實(shí)例,相當于o instanceOf
Class。
isInstance()、instanceOf關(guān)鍵字檢查的某個(gè)實(shí)例是否是這個(gè)類(lèi)型,具有繼承關(guān)系的檢查能力,
即使是其子類(lèi)的對象,也可以返回true。但是Class 對象是嚴格的類(lèi)型,所以
super.class==sub.class是一定返回false的。
l newInstance():這個(gè)方法將會(huì )使得Class 對象調用類(lèi)中的公有無(wú)參構造方法實(shí)例化
對象,,返回一個(gè)Object對象,大多數框架都會(huì )使用到這個(gè)方法,例如EJB容器、Spring
容器都會(huì )要求受管組件提供一個(gè)默認構造方法。
注意:以上的方法并不是對每種類(lèi)型都可用,對于返回數組的方法,如果不可用則返回長(cháng)度
為0 的數組,對于返回其他類(lèi)型的方法,如果不可用則返回null。例如:getConstructors()
方法對于基本數據類(lèi)型、數組就不可用,那么返回一個(gè)長(cháng)度為0的Constructor 數組。
另外,上面的方法獲取類(lèi)中的元素大都是只能獲取public的元素,可以獲取全部元素的大都
是含有Declared字符。
某些方法不具有遞歸特性,例如只能查找本類(lèi)的元素,不能查找內部類(lèi)或者其父類(lèi)中的元素,
如果你非要這么做,需要自行遞歸操作,例如:調用getSuperClass()直到其返回null為止。
______________________________________________
2.JAVA的反射機制:
前面說(shuō)過(guò)RTTI獲取某個(gè)對象的確切類(lèi)型,要求在這個(gè)對象在編譯時(shí)必須已知,也就是必須
已經(jīng)在你的代碼中存在完整的聲明(T t),但是如果是運行時(shí)才會(huì )知曉的對象(例如網(wǎng)絡(luò )中
傳遞過(guò)來(lái)的字節),RTTI就沒(méi)辦法工作了。
java.lang.Class 與java.lang.reflect.*包中的類(lèi)提供了一種有別于RTTI(編譯器在編譯器時(shí)打
開(kāi)和檢查*.class文件)的反射機制(運行時(shí)打開(kāi)和檢查*.class文件)。
JAVA的反射功能相當強大,例如前面說(shuō)過(guò)的類(lèi)的復用方式---組合,如果是動(dòng)態(tài)組合的新類(lèi)
就叫聚合,反射就可以完成聚合功能。
(1.)Field:這個(gè)類(lèi)用于獲取類(lèi)中的字段信息以及訪(fǎng)問(wèn)這些字段的能力。
l getObject(Object o):返回參數o的對象上的Field表示的字段的值;
l setObject(Object o,Object value):將參數o的對象上的Field表示的字段的值設
置為value;
l getBoolean(Object o):獲取參數o的對象的Field表示的布爾類(lèi)型的字段的值;
l setBoolean(Object o,boolean value):將參數o的對象上的Field表示的布爾類(lèi)型
的字段的值設置為value;
… …對于基本數據類(lèi)型,都擁有自己的getXXX()、setXXX()方法。
l getGenericType():返回Field表示的字段聲明的泛型類(lèi)型的Type實(shí)例;
l getModifiers():以整型數值和的形式返回Field表示的字段的修飾符;
l getType():返回Field表示的字段的類(lèi)型的Class 對象;
l isEnumConstants():如果Field表示的字段是枚舉類(lèi)型,返回true;
l toGenericString():返回描述此字段的字符串(包含泛型信息),能夠包含泛型信息
是與toString()方法的唯一區別。
例:
public class Reflect1 {
private int i;
protected String s;
public List<String> list = new ArrayList<String>();
public Reflect1() {
System.out.println("default");
}
protected Reflect1(String name) {
System.out.println(name);
}
public void f() {
System.out.println("invoke f");
}
String mf(int j, Object... args) {
System.out.println("invoke mf");
return String.valueOf(j + args.length);
}
}
public class Rf {
public static void main(String[] args) throws Exception {
Class<Reflect1> clazz = Reflect1.class;
Reflect1 rf1 = clazz.newInstance();
// 含有Declared字符串的是獲取所有的元素,否則就只能獲取公有元素
Field[] f = clazz.getDeclaredFields();
for (Field field : f) {
// 設置這里本不具有訪(fǎng)問(wèn)權限的元素為可訪(fǎng)問(wèn)
field.setAccessible(true);
// 使用基本數據類(lèi)型專(zhuān)有的API
if (field.getType().getCanonicalName().equals("int")
|| field.getType().getCanonicalName().equals(
"java.lang.Integer")) {
field.setInt(rf1, 9);
}
System.out.println("Field is " + field.getName()+ "\t"
+ field.toGenericString() + "\t" + field.get(rf1));
}
}
}
控制臺輸出如下語(yǔ)句:
default
Field is i private int net.ilkj.reflect.Reflect1.i 9
Field is s protected java.lang.String net.ilkj.reflect.Reflect1.s null
Field is list
public java.util.List<java.lang.String> net.ilkj.reflect.Reflect1.list []
______________________________________________
(2.)Method:這個(gè)類(lèi)用于獲取類(lèi)中的方法的信息以及訪(fǎng)問(wèn)這些方法的能力。
l getDefaultValue():返回此方法表示的注視成員的默認值;
l getParameterAnnotations():返回一個(gè)Annotation的二維數組,表示方法的參數列
表的參數的注解;
l getExceptionTypes():返回這個(gè)方法拋出的(底層)異常的Class數組;
l getGenericExceptionTypes():返回這個(gè)方法拋出的異常的Type數組,包含泛型信
息;
l getParameterTypes():返回這個(gè)方法的參數列表的Class對象數組;
l getGenericParameterTypes():返回這個(gè)方法的參數列表的包含泛型信息的Type
對象數組,例如一個(gè)List<String>的參數前面的方法會(huì )獲得java.util.List,而這個(gè)方
法會(huì )獲得java.util.List<java.lang.String>;
l getReturnType():獲取方法返回值的類(lèi)型的Class對象;
l getGenericReturnType():獲取方法返回值的類(lèi)型的Type對象,含有泛型信息;
l isBridge():如果此方法是bridge方法,返回true;
l isVarArgs():如果此方法的參數列表中含有可變參數,返回true;
l invoke(Object o,Object… args):使用類(lèi)的對象和可變參數列表調用這個(gè)方法,這
個(gè)方法返回Object對象,也就是類(lèi)中的這個(gè)方法的返回值;
l getTypeParameters():以java.lang.reflect.TypeVariable數組返回Method對象的泛型
方法的類(lèi)型參數,數組的順序是泛型定義的類(lèi)型的順序;
l toGenericString():返回描述此方法的字符串(包含泛型信息),能夠包含泛型信息
是此方法與toString()方法的唯一區別。
例:
public class Reflect1<X extends Exception> {
private int i;
protected String s;
public List<String> list = new ArrayList<String>();
public Reflect1() {
System.out.println("default");
}
protected Reflect1(String name) {
System.out.println(name);
}
public void f() throws NumberFormatException,
ArrayIndexOutOfBoundsException {
System.out.println("invoke f");
}
String mf(List<String> list, Object... args) throws X {
System.out.println("invoke mf");
return String.valueOf(list.size() + args.length);
}
}
public class Rf {
public static void main(String[] args) throws Exception {
Class<Reflect1> clazz = Reflect1.class;
Reflect1 rf1 = clazz.newInstance();
Method[] m = clazz.getDeclaredMethods();
for (Method method : m) {
Class[] cs = method.getParameterTypes();
System.out.println("Method is " + method.getName());
// 獲取泛型的異常
Type[] t = method.getGenericExceptionTypes();
for (Type type : t) {
System.out.println("GenericException is " + type.toString());
}
// 獲取普通的異常
Class[] cc = method.getExceptionTypes();
for (Class c : cc) {
System.out.println("Exception is " + c.getCanonicalName());
}
if (cs.length == 2) {
if (cs[0].getCanonicalName().equals("java.util.List")
&& cs[1].getCanonicalName()
.equals("java.lang.Object[]")) {
Object o = method.invoke(rf1, new ArrayList<String>(),
new Object[] { "2" });
System.out.println("The Return Value is " + o);
}
}
}
}
}
控制臺輸出如下語(yǔ)句:
default
Method is mf
GenericException is X
Exception is java.lang.Exception
invoke mf
The Return Value is 1
Method is f
GenericException is class java.lang.NumberFormatException
GenericException is class java.lang.ArrayIndexOutOfBoundsException
Exception is java.lang.NumberFormatException
Exception is java.lang.ArrayIndexOutOfBoundsException
注意紅色的代碼,你就能區分出含有Generic字符串的方法和不含有這個(gè)字符串的方法的區
別了。
______________________________________________
(3.)Constructor:這個(gè)類(lèi)用于獲取類(lèi)中的構造方法的信息以及訪(fǎng)問(wèn)這些構造方法的能力。
構造方法由于比較特殊,所以單獨作為一個(gè)類(lèi),但是它的方法和Method沒(méi)有任何區別。
______________________________________________
(4.)Member接口:Class、Field、Method、Constructor都實(shí)現了Member接口,表明他們是
類(lèi)的成員。
l getDeclaringClass():返回此成員所屬的類(lèi)的Class對象;
l getModifiers():以整型值的和的形式返回這個(gè)成員的修飾符;
l getName():返回此成員的名稱(chēng)的字符串;
l isSynthetic():如果此成員是編譯器引入的,返回true。
______________________________________________
(5.)AccessibleObject 類(lèi):Class、Field、Method(注意沒(méi)有Constructor,可見(jiàn)構造方法的訪(fǎng)
問(wèn)級別絕對不可以改變?。┒紨U展了AccessibleObject類(lèi),這個(gè)類(lèi)提供了取消JAVA訪(fǎng)問(wèn)權限
的限制。
l getAnnotation(Class<T> annotationClass):返回這個(gè)成員上指定注解類(lèi)型的注
解;
l getAnnotations():返回這個(gè)成員上的所有注解的Annotation數組;
l getDeclaredAnnotations():返回直接應用在此元素上的注解的Annotation數組;
l isAccessible():如果該成員在當前位置可以被訪(fǎng)問(wèn),返回true;
l isAnnotationPresent(Class<? extends Annotation> annotationClass):返回
指定的annotationClass是否存在于當前的成員;
l setAccessible(boolean bool):設置此元素是否可以在當前位置訪(fǎng)問(wèn),這個(gè)方法在
外面訪(fǎng)問(wèn)類(lèi)中的私有成員時(shí),非常有用處;
l setAccessible(AccessibleObject[] array,boolean flag):這是一個(gè)靜態(tài)方法,你
可以將一組成員包裝成AccessibleObject數組,一并設置他們的訪(fǎng)問(wèn)性。
______________________________________________
(6.)Modifier:這個(gè)類(lèi)存放了所有修飾符的常量值,以及通過(guò)這些常量值判斷是哪種修飾符。
你可以將getModifiers()方法返回的int 類(lèi)型傳入這個(gè)類(lèi)的isXXX()靜態(tài)方法,判斷是哪種修
飾符。
______________________________________________
(7.)Array:這個(gè)類(lèi)提供了動(dòng)態(tài)創(chuàng )建和訪(fǎng)問(wèn)數組的能力。
這個(gè)類(lèi)中的方法全部是靜態(tài)方法,它允許在get()、set()方法時(shí)進(jìn)行擴展轉型,如果進(jìn)行收縮
轉型,將會(huì )拋出非法的參數異常。這個(gè)類(lèi)的方法都是靜態(tài)方法。
l get(Object o,int index):獲取指定數組中的索引位置index上的元素;
l set(Object o,int index,Object value):設置指定數組中的索引位置index上的元素
為value;
getXXX()、setXXX()可以直接設置基本數據類(lèi)型;
l newInstance(Class componentType,int length) : 創(chuàng )建一個(gè)指定類(lèi)型為
componentType的長(cháng)度為length的一維數組;
l newInstance(Class componentType,int… args) :創(chuàng )建一個(gè)指定類(lèi)型為
componentType的args 指定的維數的多維數組;
例:
public class Rf {
public static void main(String[] args) throws Exception {
// 創(chuàng )建一個(gè)二維數組,第一維有三個(gè)元素,第二維有兩個(gè)元素
Person[][] i = (Person[][]) Array.newInstance(Person.class, 3, 2);
Array.set(i[0], 0, new Child());
System.out.println(Array.get(i[0], 0));
}
}
class Person {
}
class Child extends Person {
}
______________________________________________
3.動(dòng)態(tài)代理:
要看清楚什么是動(dòng)態(tài)代理的,首先我們來(lái)看一下靜態(tài)代理的做法。無(wú)論是那種代理方式,都
存在代理對象和目標對象兩個(gè)模型,所謂目標對象就是我們要生成的代理對象所代理的那個(gè)
對象。
(1.) 包裝的模式進(jìn)行靜態(tài)代理:
接口:Animal
public interface Animal {
void eat(String food);
String type();
}
實(shí)現類(lèi):Monkey
public class Monkey implements Animal {
@Override
public String type() {
String type = "哺乳動(dòng)物";
System.out.println(type);
return type;
}
@Override
public void eat(String food) {
System.out.println("The food is " + food + " !");
}
}
包裝類(lèi):AnimalWrapper
public class AnimalWrapper implements Animal {
private Animal animal;
// 使用構造方法包裝Animal的接口,這樣所有的Animal實(shí)現類(lèi)都可以被這個(gè)Wrapper
包裝。
public AnimalWrapper(Animal animal) {
this.animal = animal;
}
@Override
public void eat(String food) {
System.out.println("+++Wrapped Before!+++");
animal.eat(food);
System.out.println("+++Wrapped After!+++");
}
@Override
public String type() {
System.out.println("---Wrapped Before!---");
String type = animal.type();
System.out.println("---Wrapped After!---");
return type;
}
}
運行程序:
AnimalWrapper aw = new AnimalWrapper(new Monkey());
aw.eat("香蕉");
aw.type();
控制臺輸出如下語(yǔ)句:
+++Wrapped Before!+++
The food is 香蕉!
+++Wrapped After!+++
---Wrapped Before!---
哺乳動(dòng)物
---Wrapped After!---
這里我們完成了對Animal 所有子類(lèi)的代理,在代理方法中,你可以加入一些自己的額外的
處理邏輯,就像上面的+++、---輸出語(yǔ)句一樣。那么Spring的前置、后置、環(huán)繞方法通知,
通過(guò)這種方式可以有限的模擬出來(lái),以Spring 的聲明式事務(wù)為例,無(wú)非就是在調用包裝的
目標方法之前處開(kāi)啟事務(wù),在之后提交事務(wù),這樣原有的業(yè)務(wù)邏輯沒(méi)有受到任何事務(wù)管理代
碼的侵入。
這種方式的靜態(tài)代理,缺點(diǎn)就是當Animal 接口中增加了新的方法,那么包裝類(lèi)中也必須增
加這些新的方法。
(2.) 繼承的模式進(jìn)行靜態(tài)代理:
繼承類(lèi):MyMonkey
public class MyMonkey extends Monkey {
@Override
public void eat(String food) {
System.out.println("+++Wrapped Before!+++");
super.eat(food);
System.out.println("+++Wrapped After!+++");
}
@Override
public String type() {
System.out.println("---Wrapped Before!---");
String type = super.type();
System.out.println("---Wrapped After!---");
return type;
}
}
這個(gè)例子很容易看懂,我們采用繼承的方式對MyMonkey 中的方法進(jìn)行代理,運行效果與
包裝的模式效果是一樣的。
但這種方式的缺點(diǎn)更明顯,那就是不能實(shí)現對Animal 所有子類(lèi)的代理,與包裝的模式相比,
大大縮小了代理范圍。
_______________________________________________________________________________
(3.) 基于 Proxy的動(dòng)態(tài)代理:
JAVA 自帶的動(dòng)態(tài)代理是基于java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler 兩個(gè)
類(lèi)來(lái)完成的,使用JAVA 反射機制。
Proxy類(lèi)中的幾個(gè)方法都是靜態(tài)的,通常,你可以使用如下兩種模式創(chuàng )建代理對象:
①
Object proxy = Proxy.newProxyInstance(定義代理對象的類(lèi)加載器,
要代理的目標對象的歸屬接口數組,回調接口InvocationHandler);
②
Class proxyClass=Proxy.getProxyClass(定義代理對象的類(lèi)加載器,
要代理的目標對象的歸屬接口數組);
Object proxy = proxyClass.getConstructor(
new Class[] { InvocationHandler.class }).newInstance(
回調接口InvocationHandler);
第一種方式更加直接簡(jiǎn)便,并且隱藏了代理$Proxy0 對象的結構。
JDK 的動(dòng)態(tài)代理會(huì )動(dòng)態(tài)的創(chuàng )建一個(gè)$Proxy0的類(lèi),這個(gè)類(lèi)繼承了Proxy并且實(shí)現了要代理的
目標對象的接口,但你不要試圖在JDK 中查找這個(gè)類(lèi),因為它是動(dòng)態(tài)生成的。$Proxy0 的結
構大致如下所示:
public final class $Proxy0 extends Proxy implements 目標對象的接口1,接口2,…{
//構造方法
Public $Proxy0(InvocationHandler h){
……
}
}
從上面的類(lèi)結構,你就可以理解為什么第二種創(chuàng )建代理對象的方法為什么要那么寫(xiě)了。
下面我們看一個(gè)具體的實(shí)例:
接口 1:Mammal(哺乳動(dòng)物)
public interface Mammal {
void eat(String food);
String type();
}
接口 2:Primate(靈長(cháng)類(lèi)動(dòng)物)
public interface Primate {
void think();
}
實(shí)現類(lèi):Monkey
public class Monkey implements Mammal, Primate {
@Override
public String type() {
String type = "哺乳動(dòng)物";
System.out.println(type);
return type;
}
@Override
public void eat(String food) {
System.out.println("The food is " + food + " !");
}
@Override
public void think() {
System.out.println("思考!");
}
}
回調類(lèi):MyInvocationHandler
public class MyInvocationHandler implements InvocationHandler {
private Object obj;
public MyInvocationHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Invoke method Before!");
Object returnObject = method.invoke(obj, args);
System.out.println("Invoke method After!");
return returnObject;
}
}
注意:這里我們使用構造方法將要代理的目標對象傳入回調接口,當然你也可以用其他的方
式,但無(wú)論如何,一個(gè)代理對象應該是與一個(gè)回調接口對應的。
運行程序:
// 第一種創(chuàng )建動(dòng)態(tài)代理的方法
// Object proxy = Proxy.newProxyInstance(Monkey.class.getClassLoader(),
// Monkey.class.getInterfaces(), new MyInvocationHandler(
// new Monkey()));
// 第二種創(chuàng )建動(dòng)態(tài)代理的方法
Class<?> proxyClass = Proxy.getProxyClass(
Monkey.class.getClassLoader(),
Monkey.class.getInterfaces());
Object proxy = proxyClass.getConstructor(
new Class[] { InvocationHandler.class }).newInstance(
new MyInvocationHandler(new Monkey()));
Mammal mammal = (Mammal) proxy;
mammal.eat("香蕉");
mammal.type();
Primate primate = (Primate) proxy;
primate.think();
控制臺輸出:
Invoke method Before!
The food is 香蕉!
Invoke method After!
Invoke method Before!
哺乳動(dòng)物
Invoke method After!
Invoke method Before!
思考!
Invoke method After!
你可以看到動(dòng)態(tài)代理成功了,在目標對象的方法調用前后都輸出了我們打印的語(yǔ)句。其實(shí)
Spring 中對接口的動(dòng)態(tài)代理,進(jìn)而做諸如聲明式事務(wù)的AOP 操作也是如此,只不過(guò)代碼會(huì )
更加復雜。
我們用下面的圖說(shuō)明上面的執行過(guò)程:
我們看到目標對象的方法調用被Proxy攔截,在InvocationHandler 中的回調方法中通過(guò)反射
調用。這種動(dòng)態(tài)代理的方式實(shí)現了對類(lèi)的方法的運行時(shí)修改。
JDK 的動(dòng)態(tài)代理有個(gè)缺點(diǎn),那就是不能對類(lèi)進(jìn)行代理,只能對接口進(jìn)行代理,想象一下我
們的Monkey如果沒(méi)有實(shí)現任何接口,那么將無(wú)法使用這種方式進(jìn)行動(dòng)態(tài)代理(實(shí)際上是因
為$Proxy0 這個(gè)類(lèi)繼承了Proxy,JAVA 的繼承不允許出現多個(gè)父類(lèi))。但準確的說(shuō)這個(gè)問(wèn)題
不應該是缺點(diǎn),因為良好的系統,每一個(gè)類(lèi)都是應該有一個(gè)接口的。
從上面知道$Proxy0 是動(dòng)態(tài)代理對象的所屬類(lèi)型,但由于這個(gè)類(lèi)型根本不存在,我們如何鑒
別一個(gè)對象是一個(gè)普通的對象還是動(dòng)態(tài)代理對象呢?Proxy類(lèi)中提供了isProxyClass(Class c)
方法鑒別與此。
下面我們介紹一下InvocationHandler 這個(gè)接口,它只有一個(gè)方法invoke()需要實(shí)現,這個(gè)方
法會(huì )在目標對象的方法調用的時(shí)候被激活,你可以在這里控制目標對象的方法的調用,在調
用前后插入一些其他操作(譬如:鑒權、日志、事務(wù)管理等)。Invoke()方法的后兩個(gè)參數很
好理解,一個(gè)是調用的方法的Method對象,另一個(gè)是方法的參數,第一個(gè)參數有些需要注
意的地方,這個(gè)proxy 參數就是我們使用Proxy 的靜態(tài)方法創(chuàng )建的動(dòng)態(tài)代理對象,也就是
$Proxy0的實(shí)例(這點(diǎn)你可以在Eclipse的斷點(diǎn)調試中看到proxy的所屬類(lèi)型確實(shí)是$Proxy0)。
由于$Proxy0 在JDK 中不是靜態(tài)存在的,因此你不可以把第一個(gè)參數Object proxy強制轉換
為$Proxy0 類(lèi)型,因為你根本就無(wú)法從Classpath 中導入$Proxy0。那么我們可以把proxy 轉
為目標對象的接口嗎?因為$Proxy0 是實(shí)現了目標對象的所有的接口的,答案是可以的。但
實(shí)際上這樣做的意義不大,因為你會(huì )發(fā)現轉換為目標對象的接口之后,你調用接口中的任何
一個(gè)方法,都會(huì )導致invoke()的調用陷入死循環(huán)而導致堆棧溢出。如下所示:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Mammal mammal=(Mammal)proxy;
mammal.type();
… …
}
這是因為目標對象的大部分的方法都被代理了,你在invoke()通過(guò)代理對象轉換之后的接口
調用目標對象的方法,依然是走的代理對象,也就是說(shuō)當mammal.type()方法被激活時(shí)會(huì )立
即導致invoke()的調用,然后再次調用mammal.type()方法,… …從而使方法調用進(jìn)入死循
環(huán),就像無(wú)盡的遞歸調用。
那么invoke()方法的第一個(gè)參數到底干什么用的呢?其實(shí)一般情況下這個(gè)參數都用不到,除
請求代理對象
Proxy
InvocationHandler 目標對象
非你想獲得代理對象的類(lèi)信息描述,因為它的getClass()方法的調用不會(huì )陷入死循環(huán)。如下
所示:
Class<?> c = proxy.getClass();
Method[] methods = c.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m.getName());
}
這里我們可以獲得代理對象的所有的方法的名字,你會(huì )看到控制臺輸出如下信息:
eat
think
type
equals
toString
hashCode
我們看到proxy確實(shí)動(dòng)態(tài)的把目標對象的所有的接口中的方法都集中到了自己的身上。
這里還要注意一個(gè)問(wèn)題,那就是從Object身上繼承的方法hashCode()等的調用也會(huì )導致陷入
死循環(huán),為什么getClass()不會(huì )呢?因為getClass()方法是final的,不可以被覆蓋,所以也就
不會(huì )被Proxy代理。但不要認為Proxy不可以對final的方法進(jìn)行動(dòng)態(tài)代理,因為Proxy面向
的是Monkey的接口,而不是Monkey本身,所以即便是Monkey在實(shí)現Mammal、Primate
接口的時(shí)候,把方法都變?yōu)閒inal的,也不會(huì )影響到Proxy的動(dòng)態(tài)代理。
_______________________________________________________________________________
(4.) 基于 CGLIB的動(dòng)態(tài)代理:
CGLIB 是一個(gè)開(kāi)源的動(dòng)態(tài)代理框架,它的出現補充了JDK 自帶的Proxy 不能對類(lèi)實(shí)現動(dòng)態(tài)
代理的問(wèn)題。CGLIB是如何突破限制,對類(lèi)也能動(dòng)態(tài)代理的呢?這是因為CGLIB內部使用
了另一個(gè)字節碼框架ASM,類(lèi)似的字節碼框架還有Javassist、BCEL等,但ASM被認為是
性能最好的一個(gè)。但這類(lèi)字節碼框架要求你對JAVA 的Class 文件的結構、指令集都比較了
解,CGLIB 對外屏蔽了這些細節問(wèn)題。由于CGLIB 使用ASM 直接操作字節碼,因此效率
要比Proxy高,但這里所說(shuō)的效率是指代理對象的性能,在創(chuàng )建代理對象時(shí),Proxy是要比
CGLIB效率高的。
下面我們簡(jiǎn)單看一個(gè)CGLIB完成動(dòng)態(tài)代理的例子。
目標類(lèi):Monkey
public class Monkey {
public String type() {
String type = "哺乳動(dòng)物";
System.out.println(type);
return type;
}
public final void eat(String food) {
System.out.println("The food is " + food + " !");
}
public void think() {
System.out.println("思考!");
}
}
我們看到這個(gè)Monkey 類(lèi)有兩點(diǎn)變化,第一點(diǎn)是沒(méi)有實(shí)現任何接口,第二點(diǎn)是eat()方法是
final的。
回調接口:
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("******************");
Object o = proxy.invokeSuper(obj, args);
System.out.println("++++++++++++++++++");
return o;
}
}
運行程序:
import net.sf.cglib.proxy.Enhancer;
public class Cglib {
public static void main(String[] args) {
Monkey monkey = (Monkey) Enhancer.create(Monkey.class,
new MyMethodInterceptor());
monkey.eat("香蕉");
monkey.type();
monkey.think();
}
}
控制臺輸出:
The food is 香蕉!
******************
哺乳動(dòng)物
++++++++++++++++++
******************
思考!
++++++++++++++++++
你會(huì )發(fā)現eat()方法沒(méi)有被代理,因為在它的前后沒(méi)有輸出MethodInterceptor 中的打印語(yǔ)句。
這是因為CGLIB 動(dòng)態(tài)代理的原理是使用ASM 動(dòng)態(tài)生成目標對象的子類(lèi),final 方法不能被
子類(lèi)覆蓋,自然也就不能被動(dòng)態(tài)代理,這也是CGLIB的一個(gè)缺點(diǎn)。
我們看到CGLIB進(jìn)行動(dòng)態(tài)代理的編寫(xiě)過(guò)程與Proxy沒(méi)什么太大的不同,Enhancer 是CGLIB
的入口,通過(guò)它創(chuàng )建代理對象,同時(shí)為代理對象分配一個(gè)net.sf.cglib.proxy.Callback 回調接
口,用于執行回調。我們常用的是MethodInterceptor 接口,這個(gè)接口繼承自Callback接口,
用于執行方法攔截。
MethodInterceptor 接口中的intercept()方法中的參數分別為代理對象、被調用的方法的
Method對象,方法的參數、CGLIB提供的方法代理對象,一般來(lái)說(shuō)調用目標方法時(shí)我們使
用最后一個(gè)參數,而不是JAVA 反射的第二個(gè)參數,因為CGLIB使用ASM的字節碼操作,
代理對象的執行效率比反射機制更高。
(4-1.)關(guān)于Enhancer:
這個(gè)類(lèi)的靜態(tài)方法create()可以用于創(chuàng )建代理對象,CGLIB使用動(dòng)態(tài)生成子類(lèi)的方式完成動(dòng)
態(tài)代理,那么默認情況下,子類(lèi)會(huì )繼承父類(lèi)的無(wú)參構造進(jìn)行實(shí)例化,如果你想調用父類(lèi)的其
他構造器,可以使用create(Class[] argumentsType,Object[] arguments)這個(gè)重載方法分別指定
構造方法的參數類(lèi)型、傳遞參數。
但是一般情況下,我們會(huì )創(chuàng )建Enhancer 的實(shí)例來(lái)完成動(dòng)態(tài)代理,而不是使用靜態(tài)方法
create(),因為使用Enhancer 的實(shí)例,你可以獲取更多的功能。使用Enhancer 實(shí)例的代碼如
下所示:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Monkey.class);
enhancer.setCallback(new MyMethodInterceptor());
Monkey monkey = (Monkey) enhancer.create();
monkey.eat("香蕉");
monkey.type();
monkey.think();
通過(guò)Enhancer 的實(shí)例你可以設置是否使用緩存、生成策略等。
(4-2.)關(guān)于Callback:
除了MethodInterceptor 以外,CGLIB還提供了一些內置的回調處理。
l net.sf.cglib.proxy.FixedValue
為提高性能,FixedValue回調對強制某一特別方法返回固定值是有用的。
l net.sf.cglib.proxy.NoOp
NoOp回調把對方法調用直接委派到這個(gè)方法在父類(lèi)中的實(shí)現,相當于不進(jìn)行代理。
l net.sf.cglib.proxy.LazyLoader
當實(shí)際的對象需要延遲裝載時(shí),可以使用LazyLoader 回調。一旦實(shí)際對象被裝載,它將被
每一個(gè)調用代理對象的方法使用。
(4-3.)關(guān)于CallbackFilter:
Enhancer 的setCallbacks(Callback[] callbacks)方法可以為代理對象設置一組回調器,你可以
配合CallbackFilter 為不同的方法使用不同的回調器。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Monkey.class);
enhancer.setCallbacks(new Callback[] { new MyMethodInterceptor(),
NoOp.INSTANCE });
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method arg0) {
// 方法type使用回調組中的第二個(gè)回調器
if (arg0.getName().equals("type"))
return 1;
else
return 0;
}
});
Monkey monkey = (Monkey) enhancer.create();
monkey.eat("香蕉");
monkey.type();
monkey.think();
這里我們指定type()方法使用第二個(gè)回調器(也就是什么也不做的NoOp,相當于不對type()
方法進(jìn)行代理),其余的使用第一個(gè)回調器。這也就是說(shuō)CallbackFilter 的accept()方法返回
的是回調器的索引值。
CGLIB被Hibernate、Spring等很多開(kāi)源框架在內部使用,用于完成對類(lèi)的動(dòng)態(tài)代理,Spring
中的很多XML 配置屬性的proxy-target-class,默認都為false,其含義就是默認不啟用對目
標類(lèi)的動(dòng)態(tài)代理,而是對接口進(jìn)行動(dòng)態(tài)代理。某些情況下,如果你想對Struts2 的Action或
者Spring MVC 的Controller 進(jìn)行動(dòng)態(tài)代理,你會(huì )發(fā)現默認Spring 會(huì )報告找不到$Proxy0 的
xxx 方法,這是因為一般我們都不會(huì )給控制層寫(xiě)一個(gè)接口,而是直接在實(shí)現類(lèi)中寫(xiě)請求方法,
這樣JDK 自帶的Proxy 是找不到這些方法的,因為他們不在接口中,此時(shí)你就要設置
proxy-target-class=”true”,并引入CGLIB、ASM的類(lèi)庫,Spring的動(dòng)態(tài)代理就可以正常工作
了。
聯(lián)系客服