本文地址:http://m.survivalescaperooms.com/archimedes/p/java-study-note13.html,轉(zhuǎn)載請注明源地址。
Java的反射機(jī)制在Java運(yùn)行時環(huán)境中,對于任意一個類,能否知道這個類有哪些屬性和方法?對于任意一個對象,能否調(diào)用它的任意一個方法?答案是肯定的。
這種動態(tài)獲取類的信息以及動態(tài)調(diào)用對象的方法的功能來自于Java 語言的反射(Reflection)機(jī)制。
Java 反射機(jī)制主要提供了以下功能:
在運(yùn)行時判斷任意一個對象所屬的類。
在運(yùn)行時構(gòu)造任意一個類的對象。
在運(yùn)行時判斷任意一個類所具有的成員變量和方法。
在運(yùn)行時調(diào)用任意一個對象的方法
Reflection 是Java被視為動態(tài)(或準(zhǔn)動態(tài))語言的一個關(guān)鍵性質(zhì)。這個機(jī)制允許程序在運(yùn)行時透過Reflection API取得任何一個已知名稱的class的內(nèi)部信息,包括其modifiers(諸如public, static 等等)、superclass(例如Object)、實(shí)現(xiàn)之interfaces(例如Serializable),也包括fields和methods的所有信息,并可于運(yùn)行時改變fields內(nèi)容或調(diào)用methods
在JDK中,主要由以下類來實(shí)現(xiàn)Java反射機(jī)制,這些類都位于java.lang.reflect包中
Class類:代表一個類。
Field 類:代表類的成員變量(成員變量也稱為類的屬性)。
Method類:代表類的方法。
Constructor 類:代表類的構(gòu)造方法。
Array類:提供了動態(tài)創(chuàng)建數(shù)組,以及訪問數(shù)組的元素的靜態(tài)方法
PRoxy類以及InvocationHandler接口:提供了動態(tài)生成代理類以及實(shí)例的方法
其中,Class類是Reflection API 中的核心類,它有以下方法:
getName():獲得類的完整名字
getFields():獲得類的public類型的屬性
getDeclaredFields():獲得類的所有屬性
getMethods():獲得類的public類型的方法
getDeclaredMethods():獲得類的所有方法
getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name參數(shù)指定方法的名字,parameterTypes 參數(shù)指定方法的參數(shù)類型
getConstructors():獲得類的public類型的構(gòu)造方法
getConstructor(Class[] parameterTypes):獲得類的特定構(gòu)造方法,parameterTypes 參數(shù)指定構(gòu)造方法的參數(shù)類型
newInstance():通過類的不帶參數(shù)的構(gòu)造方法創(chuàng)建這個類的一個對象
每當(dāng)一個類被載入時,JVM就自動為其生成一個Class對象,通過操作class對象,我們可以得到該對象的所有成員并操作它們,舉個例子:
package javatest;import java.util.*;class Student { private String name; private int age; private int ID; public Student() { } public Student(String name, int age, int ID) { this.name = name; this.age = age; this.ID = ID; } public String getName() { return name; } public void setName(String name) { this.name = name; }}public class javatest { public static void main(String[] args) { Student s1 = new Student("java", 20, 123); Class ss = s1.getClass(); System.out.println("getName: " + ss.getName()); System.out.println("getFields: " + ss.getFields()); System.out.println("getDeclaredFields: " + ss.getDeclaredFields()); System.out.println("getMethods: " + ss.getMethods()); System.out.println("isInterface: " + ss.isInterface()); System.out.println("isPrimitive: " + ss.isPrimitive()); System.out.println("isArray: " + ss.isArray()); System.out.println("SuperClass: " + ss.getSuperclass().getName()); }}運(yùn)行結(jié)果如下:
getName: javatest.StudentgetFields: [Ljava.lang.reflect.Field;@4e25154fgetDeclaredFields: [Ljava.lang.reflect.Field;@70dea4egetMethods: [Ljava.lang.reflect.Method;@5c647e05isInterface: falseisPrimitive: falseisArray: falseSuperClass: java.lang.Object
通過反射得到類對象:
獲取方式 | 說明 | 示例 |
object.getClass() 每個對象都有此方法 | 獲取指定實(shí)例對象的Class | List list = new ArrayList(); Class listClass = list.getClass(); |
class. getSuperclass() | 獲取當(dāng)前Class的繼承類Class | List list = new ArrayList(); Class listClass = list.getClass(); Class superClass = listClass. getSuperclass(); |
Object.class | .class直接獲取 | Class listClass = ArrayList.class; |
Class.forName(類名) | 用Class的靜態(tài)方法,傳入類的全稱即可 | try { Class c = Class.forName("java.util.ArrayList"); } catch (ClassNotFoundException e) { e.printStackTrace(); } |
Primitive.TYPE | 基本數(shù)據(jù)類型的封裝類獲取Class的方式 | Class longClass = Long.TYPE; Class integerClass = Integer.TYPE; Class voidClass = Void.TYPE; |
平常情況我們通過new Object來生成一個類的實(shí)例,但有時候我們沒法直接new,只能通過反射動態(tài)生成。
通過反射實(shí)例化對象:
實(shí)例化無參構(gòu)造函數(shù)的對象,兩種方式:
① Class. newInstance();
② Class. getConstructor (new Class[]{}).newInstance(new Object[]{})
實(shí)例化帶參構(gòu)造函數(shù)的對象:
class.getConstructor(Class<?>...parameterTypes) . newInstance(Object...initargs)
接下來舉個例子實(shí)戰(zhàn)一下:
package javatest;import java.util.*;class BaseUser { public int baseId; public int getBaseId() { return baseId; } public void setBaseId(int baseId) { this.baseId = baseId; }}class User extends BaseUser { private int id; public String name; public User(){} public User(String name) { this.name = name; } private int getId() { return id; } private void serId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; }}public class javatest { public static void main(String[] args) { Class<?> userClass = User.class; try { User user = (User)userClass.newInstance(); System.out.println("1.反射實(shí)例化(無參): " + user); User user2 = (User)userClass.getConstructor(new Class[]{}).newInstance(new Object[]{}); System.out.println("2.反射實(shí)例化(無參): " + user2); User user3 = (User)userClass.getConstructor(new Class[]{String.class}).newInstance(new Object[]{"test"}); System.out.println("反射實(shí)例化(帶參): " + user3 + " 屬性Name的值: " + user3.getName()); User user4 = new User(); System.out.println("正常實(shí)例化: " + user4); } catch(Exception e) { e.printStackTrace(); } }}運(yùn)行結(jié)果如下:
1.反射實(shí)例化(無參): javatest.User@7852e9222.反射實(shí)例化(無參): javatest.User@4e25154f反射實(shí)例化(帶參): javatest.User@70dea4e 屬性Name的值: test正常實(shí)例化: javatest.User@5c647e05
通過反射調(diào)用Method(方法):
獲得當(dāng)前類以及超類的public Method:
Method[] arrMethods = classType.getMethods();
獲得當(dāng)前類申明的所有Method:
Method[] arrMethods = classType.getDeclaredMethods();
獲得當(dāng)前類以及超類指定的public Method:
Method method = classType.getMethod(Stringname, Class<?>...parameterTypes);
獲得當(dāng)前類申明的指定的Method:
Method method = classType.getDeclaredMethod(String name, Class<?>...parameterTypes)
通過反射動態(tài)運(yùn)行指定Method:
Object obj = method.invoke(Objectobj, Object...args)
例:動態(tài)操縱Method,改變一下上面的例子的主函數(shù):
public class reflectMethodDemo { public static void main(String[] args) { User user = new User(); Class<?> userClass = User.class; Method[] publicMethod = userClass.getMethods(); for(Method method : publicMethod) { System.out.println("獲得當(dāng)前類以及超類的所有publicMethod: " + method); } Method[] currentMethod = userClass.getDeclaredMethods(); for(Method method : currentMethod) { System.out.println("獲得當(dāng)前類自己聲明的所有的Method: " + method); } try { Method setBaseIdMethod = userClass.getMethod("setBaseId", new Class[]{int.class}); System.out.println("獲得當(dāng)前類或超類的public Method setBaseId: " + setBaseIdMethod); Method setIdMethod = userClass.getDeclaredMethod("setId", new Class[]{int.class}); System.out.println("獲得當(dāng)前類的Method setId: " + setIdMethod); setIdMethod.setaccessible(true); setIdMethod.invoke(user, new Object[]{110}); Method getIdMethod = userClass.getDeclaredMethod("getId", new Class[]{}); getIdMethod.setAccessible(true); Integer getId = (Integer)getIdMethod.invoke(user, new Object[]{}); System.out.println("調(diào)用getId方法獲得: " + getId); } catch(Exception e) { e.printStackTrace(); } }}通過反射調(diào)用Field(變量):
獲得當(dāng)前類以及超類的public Field:
Field[] arrFields = classType.getFields();
獲得當(dāng)前類申明的所有Field:
Field[] arrFields = classType.getDeclaredFields();
獲得當(dāng)前類以及超類指定的public Field:
Field field = classType.getField(Stringname);
獲得當(dāng)前類申明的指定的Field:
Field field = classType.getDeclaredField(String name);
通過反射動態(tài)設(shè)定Field的值:
fieldType.set(Object obj, Objectvalue);
通過反射動態(tài)獲取Field的值:
Object obj = fieldType.get(Objectobj) ;
例:動態(tài)操縱Field,改變一下上面的例子的主函數(shù):
public class reflectFieldDemo { public static void main(String[] args) { User1 user = new User1(); Class<?> userClass = user.getClass(); Field[] publicField = userClass.getFields(); for(Field field : publicField) { System.out.println("獲得該類及超類所有public Field: " + field); } Field[] currentField = userClass.getDeclaredFields(); for(Field field : currentField) { System.out.println("獲得該類自己聲明的所有Field: " + field); } try { Field baseIdField = userClass.getField("baseId"); System.out.println("獲得該類或超類名為baseId的public Field: " + baseIdField); Field idField = userClass.getDeclaredField("id"); System.out.println("獲得該類自己聲明的名為id的Field: " + idField); idField.setAccessible(true); idField.set(user, 110); Integer id = (Integer)idField.get(user); System.out.println("id的值為: " + id); } catch (Exception e) { e.printStackTrace(); } }}Java反射總結(jié):
1、只要用到反射,先獲得Class Object
2、沒有方法能獲得當(dāng)前類的超類的private方法和屬性,你必須通過getSuperclass()找到超類以后再去嘗試獲得
3、通常情況即使是當(dāng)前類,private屬性或方法也是不能訪問的,你需要設(shè)置壓制權(quán)限setAccessible(true)來取得private的訪問權(quán)。但說實(shí)話,這已經(jīng)破壞了面向?qū)ο蟮囊?guī)則,所以除非萬不得已,請盡量少用。
Java的動態(tài)代理機(jī)制代理:設(shè)計模式
代理是一種常用的設(shè)計模式,其目的就是為其他對象提供一個代理以控制對某個對象的訪問。代理類負(fù)責(zé)為委托類預(yù)處理消息,過濾消息并轉(zhuǎn)發(fā)消息,以及進(jìn)行消息被委托類執(zhí)行后的后續(xù)處理。
圖 1. 代理模式

為了保持行為的一致性,代理類和委托類通常會實(shí)現(xiàn)相同的接口,所以在訪問者看來兩者沒有絲毫的區(qū)別。通過代理類這中間一層,能有效控制對委托類對象的直接訪問,也可以很好地隱藏和保護(hù)委托類對象,同時也為實(shí)施不同控制策略預(yù)留了空間,從而在設(shè)計上獲得了更大的靈活性。Java 動態(tài)代理機(jī)制以巧妙的方式近乎完美地實(shí)踐了代理模式的設(shè)計理念。
相關(guān)的類和接口:
要了解 Java 動態(tài)代理的機(jī)制,首先需要了解以下相關(guān)的類或接口:
java.lang.reflect.Proxy:這是 Java 動態(tài)代理機(jī)制的主類,它提供了一組靜態(tài)方法來為一組接口動態(tài)地生成代理類及其對象。
清單 1. Proxy 的靜態(tài)方法
// 方法 1: 該方法用于獲取指定代理對象所關(guān)聯(lián)的調(diào)用處理器static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:該方法用于獲取關(guān)聯(lián)于指定類裝載器和一組接口的動態(tài)代理類的類對象static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:該方法用于判斷指定類對象是否是一個動態(tài)代理類static boolean isProxyClass(Class cl) // 方法 4:該方法用于為指定類裝載器、一組接口及調(diào)用處理器生成動態(tài)代理類實(shí)例static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
java.lang.reflect.InvocationHandler:這是調(diào)用處理器接口,它自定義了一個 invoke 方法,用于集中處理在動態(tài)代理類對象上的方法調(diào)用,通常在該方法中實(shí)現(xiàn)對委托類的代理訪問。
清單 2. InvocationHandler 的核心方法
// 該方法負(fù)責(zé)集中處理動態(tài)代理類上的所有方法調(diào)用。第一個參數(shù)既是代理類實(shí)例,第二個參數(shù)是被調(diào)用的方法對象// 第三個方法是調(diào)用參數(shù)。調(diào)用處理器根據(jù)這三個參數(shù)進(jìn)行預(yù)處理或分派到委托類實(shí)例上發(fā)射執(zhí)行Object invoke(Object proxy, Method method, Object[] args)
每次生成動態(tài)代理類對象時都需要指定一個實(shí)現(xiàn)了該接口的調(diào)用處理器對象(參見 Proxy 靜態(tài)方法 4 的第三個參數(shù))。
java.lang.ClassLoader:這是類裝載器類,負(fù)責(zé)將類的字節(jié)碼裝載到 Java 虛擬機(jī)(JVM)中并為其定義類對象,然后該類才能被使用。Proxy 靜態(tài)方法生成動態(tài)代理類同樣需要通過類裝載器來進(jìn)行裝載才能使用,它與普通類的唯一區(qū)別就是其字節(jié)碼是由 JVM 在運(yùn)行時動態(tài)生成的而非預(yù)存在于任何一個 .class 文件中。
每次生成動態(tài)代理類對象時都需要指定一個類裝載器對象(參見 Proxy 靜態(tài)方法 4 的第一個參數(shù))
代理機(jī)制及其特點(diǎn):
首先讓我們來了解一下如何使用 Java 動態(tài)代理。具體有如下四步驟:
1、通過實(shí)現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器;
2、通過為 Proxy 類指定 ClassLoader 對象和一組 interface 來創(chuàng)建動態(tài)代理類;
3、通過反射機(jī)制獲得動態(tài)代理類的構(gòu)造函數(shù),其唯一參數(shù)類型是調(diào)用處理器接口類型;
4、通過構(gòu)造函數(shù)創(chuàng)建動態(tài)代理類實(shí)例,構(gòu)造時調(diào)用處理器對象作為參數(shù)被傳入。
清單 3. 動態(tài)代理對象創(chuàng)建過程
// InvocationHandlerImpl 實(shí)現(xiàn)了 InvocationHandler 接口,并能實(shí)現(xiàn)方法調(diào)用從代理類到委托類的分派轉(zhuǎn)發(fā)// 其內(nèi)部通常包含指向委托類實(shí)例的引用,用于真正執(zhí)行分派轉(zhuǎn)發(fā)過來的方法調(diào)用InvocationHandler handler = new InvocationHandlerImpl(..); // 通過 Proxy 為包括 Interface 接口在內(nèi)的一組接口動態(tài)創(chuàng)建代理類的類對象Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); // 通過反射從生成的類對象獲得構(gòu)造函數(shù)對象Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); // 通過構(gòu)造函數(shù)對象創(chuàng)建動態(tài)代理類實(shí)例Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });實(shí)際使用過程更加簡單,因為 Proxy 的靜態(tài)方法 newProxyInstance 已經(jīng)為我們封裝了步驟 2 到步驟 4 的過程,所以簡化后的過程如下
清單 4. 簡化的動態(tài)代理對象創(chuàng)建過程
// InvocationHandlerImpl 實(shí)現(xiàn)了 InvocationHandler 接口,并能實(shí)現(xiàn)方法調(diào)用從代理類到委托類的分派轉(zhuǎn)發(fā)InvocationHandler handler = new InvocationHandlerImpl(..); // 通過 Proxy 直接創(chuàng)建動態(tài)代理類實(shí)例Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );接下來讓我們來了解一下 Java 動態(tài)代理機(jī)制的一些特點(diǎn)。
首先是動態(tài)生成的代理類本身的一些特點(diǎn)。
1)包:如果所代理的接口都是 public 的,那么它將被定義在頂層包(即包路徑為空),如果所代理的接口中有非 public 的接口(因為接口不能被定義為 protect 或 private,所以除 public 之外就是默認(rèn)的 package 訪問級別),那么它將被定義在該接口所在包(假設(shè)代理了 com.VEVb.wu包中的某非 public 接口 A,那么新生成的代理類所在的包就是 com.VEVb.wu),這樣設(shè)計的目的是為了最大程度的保證動態(tài)代理類不會因為包管理的問題而無法被成功定義并訪問;
2)類修飾符:該代理類具有 final 和 public 修飾符,意味著它可以被所有的類訪問,但是不能被再度繼承;
3)類名:格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯?dāng)?shù)字,代表 Proxy 類第 N 次生成的動態(tài)代理類,值得注意的一點(diǎn)是,并不是每次調(diào)用 Proxy 的靜態(tài)方法創(chuàng)建動態(tài)代理類都會使得 N 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復(fù)創(chuàng)建動態(tài)代理類,它會很聰明地返回先前已經(jīng)創(chuàng)建好的代理類的類對象,而不會再嘗試去創(chuàng)建一個全新的代理類,這樣可以節(jié)省不必要的代碼重復(fù)生成,提高了代理類的創(chuàng)建效率。
4)類繼承關(guān)系:該類的繼承關(guān)系如圖:
圖 2. 動態(tài)代理類的繼承圖

由圖可見,Proxy 類是它的父類,這個規(guī)則適用于所有由 Proxy 創(chuàng)建的動態(tài)代理類。而且該類還實(shí)現(xiàn)了其所代理的一組接口,這就是為什么它能夠被安全地類型轉(zhuǎn)換到其所代理的某接口的根本原因。
接下來讓我們了解一下代理類實(shí)例的一些特點(diǎn)。每個實(shí)例都會關(guān)聯(lián)一個調(diào)用處理器對象,可以通過 Proxy 提供的靜態(tài)方法 getInvocationHandler 去獲得代理類實(shí)例的調(diào)用處理器對象。在代理類實(shí)例上調(diào)用其代理的接口中所聲明的方法時,這些方法最終都會由調(diào)用處理器的 invoke 方法執(zhí)行,此外,值得注意的是,代理類的根類 java.lang.Object 中有三個方法也同樣會被分派到調(diào)用處理器的 invoke 方法執(zhí)行,它們是 hashCode,equals 和 toString,可能的原因有:一是因為這些方法為 public 且非 final 類型,能夠被代理類覆蓋;二是因為這些方法往往呈現(xiàn)出一個類的某種特征屬性,具有一定的區(qū)分度,所以為了保證代理類與委托類對外的一致性,這三個方法也應(yīng)該被分派到委托類執(zhí)行。當(dāng)代理的一組接口有重復(fù)聲明的方法且該方法被調(diào)用時,代理類總是從排在最前面的接口中獲取方法對象并分派給調(diào)用處理器,而無論代理類實(shí)例是否正在以該接口(或繼承于該接口的某子接口)的形式被外部引用,因為在代理類內(nèi)部無法區(qū)分其當(dāng)前的被引用類型。
接著來了解一下被代理的一組接口有哪些特點(diǎn)。首先,要注意不能有重復(fù)的接口,以避免動態(tài)代理類代碼生成時的編譯錯誤。其次,這些接口對于類裝載器必須可見,否則類裝載器將無法鏈接它們,將會導(dǎo)致類定義失敗。再次,需被代理的所有非 public 的接口必須在同一個包中,否則代理類生成也會失敗。最后,接口的數(shù)目不能超過 65535,這是 JVM 設(shè)定的限制。
最后再來了解一下異常處理方面的特點(diǎn)。從調(diào)用處理器接口聲明的方法中可以看到理論上它能夠拋出任何類型的異常,因為所有的異常都繼承于 Throwable 接口,但事實(shí)是否如此呢?答案是否定的,原因是我們必須遵守一個繼承原則:即子類覆蓋父類或?qū)崿F(xiàn)父接口的方法時,拋出的異常必須在原方法支持的異常列表之內(nèi)。所以雖然調(diào)用處理器理論上講能夠,但實(shí)際上往往受限制,除非父接口中的方法支持拋 Throwable 異常。那么如果在 invoke 方法中的確產(chǎn)生了接口方法聲明中不支持的異常,那將如何呢?放心,Java 動態(tài)代理類已經(jīng)為我們設(shè)計好了解決方法:它將會拋出 UndeclaredThrowableException 異常。這個異常是一個 RuntimeException 類型,所以不會引起編譯錯誤。通過該異常的 getCause 方法,還可以獲得原來那個不受支持的異常對象,以便于錯誤診斷。
機(jī)制和特點(diǎn)都介紹過了,接下來讓我們通過源代碼來了解一下 Proxy 到底是如何實(shí)現(xiàn)的。
首先記住 Proxy 的幾個重要的靜態(tài)變量:
清單 5. Proxy 的重要靜態(tài)變量
// 映射表:用于維護(hù)類裝載器對象到其對應(yīng)的代理類緩存private static Map loaderToCache = new WeakHashMap(); // 標(biāo)記:用于標(biāo)記一個動態(tài)代理類正在被創(chuàng)建中private static Object pendingGenerationMarker = new Object(); // 同步表:記錄已經(jīng)被創(chuàng)建的動態(tài)代理類類型,主要被方法 isProxyClass 進(jìn)行相關(guān)的判斷private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); // 關(guān)聯(lián)的調(diào)用處理器引用protected InvocationHandler h;
然后,來看一下 Proxy 的構(gòu)造方法:
清單 6. Proxy 構(gòu)造方法
// 由于 Proxy 內(nèi)部從不直接調(diào)用構(gòu)造函數(shù),所以 private 類型意味著禁止任何調(diào)用private Proxy() {} // 由于 Proxy 內(nèi)部從不直接調(diào)用構(gòu)造函數(shù),所以 protected 意味著只有子類可以調(diào)用protected Proxy(InvocationHandler h) {this.h = h;}接著,可以快速瀏覽一下 newProxyInstance 方法,因為其相當(dāng)簡單:
清單 7. Proxy 靜態(tài)方法 newProxyInstance
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 檢查 h 不為空,否則拋異常 if (h == null) { throw new NullPointerException(); } // 獲得與制定類裝載器和一組接口相關(guān)的代理類類型對象 Class cl = getProxyClass(loader, interfaces); // 通過反射獲取構(gòu)造函數(shù)對象并生成代理類實(shí)例 try { Constructor cons = cl.getConstructor(constructorParams); return (Object) cons.newInstance(new Object[] { h }); } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } catch (IllegalAccessException e) { throw new InternalError(e.toString()); } catch (InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { throw new InternalError(e.toString()); } }由此可見,動態(tài)代理真正的關(guān)鍵是在 getProxyClass 方法,該方法負(fù)責(zé)為一組接口動態(tài)地生成代理類類型對象。在該方法內(nèi)部,您將能看到 Proxy 內(nèi)的各路英雄(靜態(tài)變量)悉數(shù)登場。該方法總共可以分為四個步驟:
1、對這組接口進(jìn)行一定程度的安全檢查,包括檢查接口類對象是否對類裝載器可見并且與類裝載器所能識別的接口類對象是完全相同的,還會檢查確保是 interface 類型而不是 class 類型。這個步驟通過一個循環(huán)來完成,檢查通過后將會得到一個包含所有接口名稱的字符串?dāng)?shù)組,記為String[] interfaceNames。總體上這部分實(shí)現(xiàn)比較直觀,所以略去大部分代碼,僅保留留如何判斷某類或接口是否對特定類裝載器可見的相關(guān)代碼。
清單 8. 通過 Class.forName 方法判接口的可見性
try { // 指定接口名字、類裝載器對象,同時制定 initializeBoolean 為 false 表示無須初始化類 // 如果方法返回正常這表示可見,否則會拋出 ClassNotFoundException 異常表示不可見 interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { }2、從 loaderToCache 映射表中獲取以類裝載器對象為關(guān)鍵字所對應(yīng)的緩存表,如果不存在就創(chuàng)建一個新的緩存表并更新到 loaderToCache。緩存表是一個 HashMap 實(shí)例,正常情況下它將存放鍵值對(接口名字列表,動態(tài)生成的代理類的類對象引用)。當(dāng)代理類正在被創(chuàng)建時它會臨時保存(接口名字列表,pendingGenerationMarker)。標(biāo)記 pendingGenerationMarke 的作用是通知后續(xù)的同類請求(接口數(shù)組相同且組內(nèi)接口排列順序也相同)代理類正在被創(chuàng)建,請保持等待直至創(chuàng)建完成。
清單 9. 緩存表的使用
do { // 以接口名字列表作為關(guān)鍵字獲得對應(yīng) cache 值 Object value = cache.get(key); if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } if (proxyClass != null) { // 如果已經(jīng)創(chuàng)建,直接返回 return proxyClass; } else if (value == pendingGenerationMarker) { // 代理類正在被創(chuàng)建,保持等待 try { cache.wait(); } catch (InterruptedException e) { } // 等待被喚醒,繼續(xù)循環(huán)并通過二次檢查以確保創(chuàng)建完成,否則重新等待 continue; } else { // 標(biāo)記代理類正在被創(chuàng)建 cache.put(key, pendingGenerationMarker); // break 跳出循環(huán)已進(jìn)入創(chuàng)建過程 break; } while (true);3、動態(tài)創(chuàng)建代理類的類對象。首先是確定代理類所在的包,其原則如前所述,如果都為 public 接口,則包名為空字符串表示頂層包;如果所有非 public 接口都在同一個包,則包名與這些接口的包名相同;如果有多個非 public 接口且不同包,則拋異常終止代理類的生成。確定了包后,就開始生成代理類的類名,同樣如前所述按格式“$ProxyN”生成。類名也確定了,接下來就是見證奇跡的發(fā)生 —— 動態(tài)生成代理類:
清單 10. 動態(tài)生成代理類
// 動態(tài)地生成代理類的字節(jié)碼數(shù)組byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { // 動態(tài)地定義新生成的代理類 proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } // 把生成的代理類的類對象記錄進(jìn) proxyClasses 表proxyClasses.put(proxyClass, null);由此可見,所有的代碼生成的工作都由神秘的 ProxyGenerator 所完成了,當(dāng)你嘗試去探索這個類時,你所能獲得的信息僅僅是它位于并未公開的 sun.misc 包,有若干常量、變量和方法以完成這個神奇的代碼生成的過程,但是 sun 并沒有提供源代碼以供研讀。至于動態(tài)類的定義,則由 Proxy 的 native 靜態(tài)方法 defineClass0 執(zhí)行。
代碼生成過程進(jìn)入結(jié)尾部分,根據(jù)結(jié)果更新緩存表,如果成功則將代理類的類對象引用更新進(jìn)緩存表,否則清楚緩存表中對應(yīng)關(guān)鍵值,最后喚醒所有可能的正在等待的線程。
走完了以上四個步驟后,至此,所有的代理類生成細(xì)節(jié)都已介紹完畢,剩下的靜態(tài)方法如 getInvocationHandler 和 isProxyClass 就顯得如此的直觀,只需通過查詢相關(guān)變量就可以完成,所以對其的代碼分析就省略了。
代理類實(shí)現(xiàn)推演:
分析了 Proxy 類的源代碼,相信對 Java 動態(tài)代理機(jī)制形成一個更加清晰的理解,整理一下思緒,一起來完成一次完整的推演過程吧。
清單 11. 代理類中方法調(diào)用的分派轉(zhuǎn)發(fā)推演實(shí)現(xiàn)
// 假設(shè)需代理接口 Simulator public interface Simulator { short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB;} // 假設(shè)代理類為 SimulatorProxy, 其類聲明將如下final public class SimulatorProxy implements Simulator { // 調(diào)用處理器對象的引用 protected InvocationHandler handler; // 以調(diào)用處理器為參數(shù)的構(gòu)造函數(shù) public SimulatorProxy(InvocationHandler handler){ this.handler = handler; } // 實(shí)現(xiàn)接口方法 simulate public short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB { // 第一步是獲取 simulate 方法的 Method 對象 java.lang.reflect.Method method = null; try{ method = Simulator.class.getMethod( "simulate", new Class[] {int.class, long.class, String.class} ); } catch(Exception e) { // 異常處理 1(略) } // 第二步是調(diào)用 handler 的 invoke 方法分派轉(zhuǎn)發(fā)方法調(diào)用 Object r = null; try { r = handler.invoke(this, method, // 對于原始類型參數(shù)需要進(jìn)行裝箱操作 new Object[] {new Integer(arg1), new Long(arg2), arg3}); }catch(Throwable e) { // 異常處理 2(略) } // 第三步是返回結(jié)果(返回類型是原始類型則需要進(jìn)行拆箱操作) return ((Short)r).shortValue(); } }模擬推演為了突出通用邏輯所以更多地關(guān)注正常流程,而淡化了錯誤處理,但在實(shí)際中錯誤處理同樣非常重要。從以上的推演中我們可以得出一個非常通用的結(jié)構(gòu)化流程:第一步從代理接口獲取被調(diào)用的方法對象,第二步分派方法到調(diào)用處理器執(zhí)行,第三步返回結(jié)果。在這之中,所有的信息都是可以已知的,比如接口名、方法名、參數(shù)類型、返回類型以及所需的裝箱和拆箱操作。
接下來讓我們把注意力重新回到先前被淡化的錯誤處理上來。在異常處理 1 處,由于我們有理由確保所有的信息如接口名、方法名和參數(shù)類型都準(zhǔn)確無誤,所以這部分異常發(fā)生的概率基本為零,所以基本可以忽略。而異常處理 2 處,我們需要思考得更多一些。回想一下,接口方法可能聲明支持一個異常列表,而調(diào)用處理器 invoke 方法又可能拋出與接口方法不支持的異常,再回想一下先前提及的 Java 動態(tài)代理的關(guān)于異常處理的特點(diǎn),對于不支持的異常,必須拋 UndeclaredThrowableException 運(yùn)行時異常。所以通過再次推演,我們可以得出一個更加清晰的異常處理 2 的情況:
清單 12. 細(xì)化的異常處理 2
Object r = null; try { r = handler.invoke(this, method, new Object[] {new Integer(arg1), new Long(arg2), arg3}); } catch( ExceptionA e) { // 接口方法支持 ExceptionA,可以拋出 throw e; } catch( ExceptionB e ) { // 接口方法支持 ExceptionB,可以拋出 throw e; } catch(Throwable e) { // 其他不支持的異常,一律拋 UndeclaredThrowableException throw new UndeclaredThrowableException(e); }參考資料http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/
您還可能感興趣:
java學(xué)習(xí)筆記系列:
java學(xué)習(xí)筆記12--異常處理
java學(xué)習(xí)筆記11--集合總結(jié)
java學(xué)習(xí)筆記10--泛型總結(jié)
java學(xué)習(xí)筆記9--內(nèi)部類總結(jié)
java學(xué)習(xí)筆記8--接口總結(jié)
java學(xué)習(xí)筆記7--抽象類與抽象方法
java學(xué)習(xí)筆記6--類的繼承、Object類
java學(xué)習(xí)筆記5--類的方法
java學(xué)習(xí)筆記4--對象的初始化與回收
java學(xué)習(xí)筆記3--類與對象的基礎(chǔ)
java學(xué)習(xí)筆記2--數(shù)據(jù)類型、數(shù)組
java學(xué)習(xí)筆記1--開發(fā)環(huán)境平臺總結(jié)
新聞熱點(diǎn)
疑難解答