本文地址:http://www.cnblogs.com/archimedes/p/java-study-note13.html,轉載請注明源地址。
Java的反射機制
在Java運行時環境中,對於任意一個類,能否知道這個類有哪些屬性和方法?對於任意一個對象,能否調用它的任意一個方法?答案是肯定的。
這種動態獲取類的信息以及動態調用對象的方法的功能來自於Java 語言的反射(Reflection)機制。
Java 反射機制主要提供了以下功能:
在運行時判斷任意一個對象所屬的類。
在運行時構造任意一個類的對象。
在運行時判斷任意一個類所具有的成員變量和方法。
在運行時調用任意一個對象的方法
Reflection 是Java被視為動態(或准動態)語言的一個關鍵性質。這個機制允許程序在運行時透過Reflection API取得任何一個已知名稱的class的內部信息,包括其modifiers(諸如public, static 等等)、superclass(例如Object)、實現之interfaces(例如Serializable),也包括fields和methods的所有信息,並可於運行時改變fields內容或調用methods
在JDK中,主要由以下類來實現Java反射機制,這些類都位於java.lang.reflect包中
Class類:代表一個類。
Field 類:代表類的成員變量(成員變量也稱為類的屬性)。
Method類:代表類的方法。
Constructor 類:代表類的構造方法。
Array類:提供了動態創建數組,以及訪問數組的元素的靜態方法
Proxy類以及InvocationHandler接口:提供了動態生成代理類以及實例的方法
其中,Class類是Reflection API 中的核心類,它有以下方法:
-
getName():獲得類的完整名字
-
getFields():獲得類的public類型的屬性
-
getDeclaredFields():獲得類的所有屬性
-
getMethods():獲得類的public類型的方法
-
getDeclaredMethods():獲得類的所有方法
-
getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name參數指定方法的名字,parameterTypes 參數指定方法的參數類型
-
getConstructors():獲得類的public類型的構造方法
-
getConstructor(Class[] parameterTypes):獲得類的特定構造方法,parameterTypes 參數指定構造方法的參數類型
-
newInstance():通過類的不帶參數的構造方法創建這個類的一個對象
每當一個類被載入時,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()); } }
運行結果如下:
getName: javatest.Student
getFields: [Ljava.lang.reflect.Field;@4e25154f
getDeclaredFields: [Ljava.lang.reflect.Field;@70dea4e
getMethods: [Ljava.lang.reflect.Method;@5c647e05
isInterface: false
isPrimitive: false
isArray: false
SuperClass: java.lang.Object
通過反射得到類對象:
獲取方式 |
說明 |
示例 |
object.getClass() 每個對象都有此方法 |
獲取指定實例對象的Class |
List list = new ArrayList(); Class listClass = list.getClass(); |
class. getSuperclass() |
獲取當前Class的繼承類Class |
List list = new ArrayList(); Class listClass = list.getClass(); Class superClass = listClass. getSuperclass(); |
Object.class |
.class直接獲取 |
Class listClass = ArrayList.class; |
Class.forName(類名) |
用Class的靜態方法,傳入類的全稱即可 |
try { Class c = Class.forName("java.util.ArrayList"); } catch (ClassNotFoundException e) { e.printStackTrace(); } |
Primitive.TYPE |
基本數據類型的封裝類獲取Class的方式 |
Class longClass = Long.TYPE; Class integerClass = Integer.TYPE; Class voidClass = Void.TYPE; |
平常情況我們通過new Object來生成一個類的實例,但有時候我們沒法直接new,只能通過反射動態生成。
通過反射實例化對象:
實例化無參構造函數的對象,兩種方式:
① Class. newInstance();
② Class. getConstructor (new Class[]{}).newInstance(new Object[]{})
實例化帶參構造函數的對象:
class.getConstructor(Class<?>... parameterTypes) . newInstance(Object... initargs)
接下來舉個例子實戰一下:
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.反射實例化(無參): " + user); User user2 = (User)userClass.getConstructor(new Class[]{}).newInstance(new Object[]{}); System.out.println("2.反射實例化(無參): " + user2); User user3 = (User)userClass.getConstructor(new Class[]{String.class}).newInstance(new Object[]{"test"}); System.out.println("反射實例化(帶參): " + user3 + " 屬性Name的值: " + user3.getName()); User user4 = new User(); System.out.println("正常實例化: " + user4); } catch(Exception e) { e.printStackTrace(); } } }
運行結果如下:
1.反射實例化(無參): javatest.User@7852e922
2.反射實例化(無參): javatest.User@4e25154f
反射實例化(帶參): javatest.User@70dea4e 屬性Name的值: test
正常實例化: javatest.User@5c647e05
通過反射調用Method(方法):
獲得當前類以及超類的public Method:
Method[] arrMethods = classType.getMethods();
獲得當前類申明的所有Method:
Method[] arrMethods = classType.getDeclaredMethods();
獲得當前類以及超類指定的public Method:
Method method = classType.getMethod(String name, Class<?>... parameterTypes);
獲得當前類申明的指定的Method:
Method method = classType.getDeclaredMethod(String name, Class<?>... parameterTypes)
通過反射動態運行指定Method:
Object obj = method.invoke(Object obj, Object... args)
例:動態操縱Method,改變一下上面的例子的主函數:
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("獲得當前類以及超類的所有publicMethod: " + method); } Method[] currentMethod = userClass.getDeclaredMethods(); for(Method method : currentMethod) { System.out.println("獲得當前類自己聲明的所有的Method: " + method); } try { Method setBaseIdMethod = userClass.getMethod("setBaseId", new Class[]{int.class}); System.out.println("獲得當前類或超類的public Method setBaseId: " + setBaseIdMethod); Method setIdMethod = userClass.getDeclaredMethod("setId", new Class[]{int.class}); System.out.println("獲得當前類的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("調用getId方法獲得: " + getId); } catch(Exception e) { e.printStackTrace(); } } }
通過反射調用Field(變量):
獲得當前類以及超類的public Field:
Field[] arrFields = classType.getFields();
獲得當前類申明的所有Field:
Field[] arrFields = classType.getDeclaredFields();
獲得當前類以及超類指定的public Field:
Field field = classType.getField(String name);
獲得當前類申明的指定的Field:
Field field = classType.getDeclaredField(String name);
通過反射動態設定Field的值:
fieldType.set(Object obj, Object value);
通過反射動態獲取Field的值:
Object obj = fieldType.get(Object obj) ;
例:動態操縱Field,改變一下上面的例子的主函數:
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反射總結:
1、只要用到反射,先獲得Class Object
2、沒有方法能獲得當前類的超類的private方法和屬性,你必須通過getSuperclass()找到超類以后再去嘗試獲得
3、通常情況即使是當前類,private屬性或方法也是不能訪問的,你需要設置壓制權限setAccessible(true)來取得private的訪問權。但說實話,這已經破壞了面向對象的規則,所以除非萬不得已,請盡量少用。
Java的動態代理機制
代理:設計模式
代理是一種常用的設計模式,其目的就是為其他對象提供一個代理以控制對某個對象的訪問。代理類負責為委托類預處理消息,過濾消息並轉發消息,以及進行消息被委托類執行后的后續處理。
圖 1. 代理模式
為了保持行為的一致性,代理類和委托類通常會實現相同的接口,所以在訪問者看來兩者沒有絲毫的區別。通過代理類這中間一層,能有效控制對委托類對象的直接訪問,也可以很好地隱藏和保護委托類對象,同時也為實施不同控制策略預留了空間,從而在設計上獲得了更大的靈活性。Java 動態代理機制以巧妙的方式近乎完美地實踐了代理模式的設計理念。
相關的類和接口:
要了解 Java 動態代理的機制,首先需要了解以下相關的類或接口:
java.lang.reflect.Proxy:這是 Java 動態代理機制的主類,它提供了一組靜態方法來為一組接口動態地生成代理類及其對象。
清單 1. Proxy 的靜態方法
// 方法 1: 該方法用於獲取指定代理對象所關聯的調用處理器 static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:該方法用於獲取關聯於指定類裝載器和一組接口的動態代理類的類對象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:該方法用於判斷指定類對象是否是一個動態代理類 static boolean isProxyClass(Class cl) // 方法 4:該方法用於為指定類裝載器、一組接口及調用處理器生成動態代理類實例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
java.lang.reflect.InvocationHandler:這是調用處理器接口,它自定義了一個 invoke 方法,用於集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委托類的代理訪問。
清單 2. InvocationHandler 的核心方法
// 該方法負責集中處理動態代理類上的所有方法調用。第一個參數既是代理類實例,第二個參數是被調用的方法對象 // 第三個方法是調用參數。調用處理器根據這三個參數進行預處理或分派到委托類實例上發射執行 Object invoke(Object proxy, Method method, Object[] args)
每次生成動態代理類對象時都需要指定一個實現了該接口的調用處理器對象(參見 Proxy 靜態方法 4 的第三個參數)。
java.lang.ClassLoader:這是類裝載器類,負責將類的字節碼裝載到 Java 虛擬機(JVM)中並為其定義類對象,然后該類才能被使用。Proxy 靜態方法生成動態代理類同樣需要通過類裝載器來進行裝載才能使用,它與普通類的唯一區別就是其字節碼是由 JVM 在運行時動態生成的而非預存在於任何一個 .class 文件中。
每次生成動態代理類對象時都需要指定一個類裝載器對象(參見 Proxy 靜態方法 4 的第一個參數)
代理機制及其特點:
首先讓我們來了解一下如何使用 Java 動態代理。具體有如下四步驟:
1、通過實現 InvocationHandler 接口創建自己的調用處理器;
2、通過為 Proxy 類指定 ClassLoader 對象和一組 interface 來創建動態代理類;
3、通過反射機制獲得動態代理類的構造函數,其唯一參數類型是調用處理器接口類型;
4、通過構造函數創建動態代理類實例,構造時調用處理器對象作為參數被傳入。
清單 3. 動態代理對象創建過程
// InvocationHandlerImpl 實現了 InvocationHandler 接口,並能實現方法調用從代理類到委托類的分派轉發 // 其內部通常包含指向委托類實例的引用,用於真正執行分派轉發過來的方法調用 InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過 Proxy 為包括 Interface 接口在內的一組接口動態創建代理類的類對象 Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
// 通過反射從生成的類對象獲得構造函數對象 Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
// 通過構造函數對象創建動態代理類實例 Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
實際使用過程更加簡單,因為 Proxy 的靜態方法 newProxyInstance 已經為我們封裝了步驟 2 到步驟 4 的過程,所以簡化后的過程如下
清單 4. 簡化的動態代理對象創建過程
// InvocationHandlerImpl 實現了 InvocationHandler 接口,並能實現方法調用從代理類到委托類的分派轉發 InvocationHandler handler = new InvocationHandlerImpl(..);
// 通過 Proxy 直接創建動態代理類實例 Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );
接下來讓我們來了解一下 Java 動態代理機制的一些特點。
首先是動態生成的代理類本身的一些特點。
1)包:如果所代理的接口都是 public 的,那么它將被定義在頂層包(即包路徑為空),如果所代理的接口中有非 public 的接口(因為接口不能被定義為 protect 或 private,所以除 public 之外就是默認的 package 訪問級別),那么它將被定義在該接口所在包(假設代理了 com.cnblogs.wu 包中的某非 public 接口 A,那么新生成的代理類所在的包就是 com.cnblogs.wu),這樣設計的目的是為了最大程度的保證動態代理類不會因為包管理的問題而無法被成功定義並訪問;
2)類修飾符:該代理類具有 final 和 public 修飾符,意味着它可以被所有的類訪問,但是不能被再度繼承;
3)類名:格式是“$ProxyN”,其中 N 是一個逐一遞增的阿拉伯數字,代表 Proxy 類第 N 次生成的動態代理類,值得注意的一點是,並不是每次調用 Proxy 的靜態方法創建動態代理類都會使得 N 值增加,原因是如果對同一組接口(包括接口排列的順序相同)試圖重復創建動態代理類,它會很聰明地返回先前已經創建好的代理類的類對象,而不會再嘗試去創建一個全新的代理類,這樣可以節省不必要的代碼重復生成,提高了代理類的創建效率。
4)類繼承關系:該類的繼承關系如圖:
圖 2. 動態代理類的繼承圖
由圖可見,Proxy 類是它的父類,這個規則適用於所有由 Proxy 創建的動態代理類。而且該類還實現了其所代理的一組接口,這就是為什么它能夠被安全地類型轉換到其所代理的某接口的根本原因。
接下來讓我們了解一下代理類實例的一些特點。每個實例都會關聯一個調用處理器對象,可以通過 Proxy 提供的靜態方法 getInvocationHandler 去獲得代理類實例的調用處理器對象。在代理類實例上調用其代理的接口中所聲明的方法時,這些方法最終都會由調用處理器的 invoke 方法執行,此外,值得注意的是,代理類的根類 java.lang.Object 中有三個方法也同樣會被分派到調用處理器的 invoke 方法執行,它們是 hashCode,equals 和 toString,可能的原因有:一是因為這些方法為 public 且非 final 類型,能夠被代理類覆蓋;二是因為這些方法往往呈現出一個類的某種特征屬性,具有一定的區分度,所以為了保證代理類與委托類對外的一致性,這三個方法也應該被分派到委托類執行。當代理的一組接口有重復聲明的方法且該方法被調用時,代理類總是從排在最前面的接口中獲取方法對象並分派給調用處理器,而無論代理類實例是否正在以該接口(或繼承於該接口的某子接口)的形式被外部引用,因為在代理類內部無法區分其當前的被引用類型。
接着來了解一下被代理的一組接口有哪些特點。首先,要注意不能有重復的接口,以避免動態代理類代碼生成時的編譯錯誤。其次,這些接口對於類裝載器必須可見,否則類裝載器將無法鏈接它們,將會導致類定義失敗。再次,需被代理的所有非 public 的接口必須在同一個包中,否則代理類生成也會失敗。最后,接口的數目不能超過 65535,這是 JVM 設定的限制。
最后再來了解一下異常處理方面的特點。從調用處理器接口聲明的方法中可以看到理論上它能夠拋出任何類型的異常,因為所有的異常都繼承於 Throwable 接口,但事實是否如此呢?答案是否定的,原因是我們必須遵守一個繼承原則:即子類覆蓋父類或實現父接口的方法時,拋出的異常必須在原方法支持的異常列表之內。所以雖然調用處理器理論上講能夠,但實際上往往受限制,除非父接口中的方法支持拋 Throwable 異常。那么如果在 invoke 方法中的確產生了接口方法聲明中不支持的異常,那將如何呢?放心,Java 動態代理類已經為我們設計好了解決方法:它將會拋出 UndeclaredThrowableException 異常。這個異常是一個 RuntimeException 類型,所以不會引起編譯錯誤。通過該異常的 getCause 方法,還可以獲得原來那個不受支持的異常對象,以便於錯誤診斷。
機制和特點都介紹過了,接下來讓我們通過源代碼來了解一下 Proxy 到底是如何實現的。
首先記住 Proxy 的幾個重要的靜態變量:
清單 5. Proxy 的重要靜態變量
// 映射表:用於維護類裝載器對象到其對應的代理類緩存 private static Map loaderToCache = new WeakHashMap();
// 標記:用於標記一個動態代理類正在被創建中 private static Object pendingGenerationMarker = new Object();
// 同步表:記錄已經被創建的動態代理類類型,主要被方法 isProxyClass 進行相關的判斷 private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());
// 關聯的調用處理器引用 protected InvocationHandler h;
然后,來看一下 Proxy 的構造方法:
清單 6. Proxy 構造方法
// 由於 Proxy 內部從不直接調用構造函數,所以 private 類型意味着禁止任何調用 private Proxy() {} // 由於 Proxy 內部從不直接調用構造函數,所以 protected 意味着只有子類可以調用 protected Proxy(InvocationHandler h) {this.h = h;}
接着,可以快速瀏覽一下 newProxyInstance 方法,因為其相當簡單:
清單 7. Proxy 靜態方法 newProxyInstance
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 檢查 h 不為空,否則拋異常 if (h == null) { throw new NullPointerException(); } // 獲得與制定類裝載器和一組接口相關的代理類類型對象 Class cl = getProxyClass(loader, interfaces); // 通過反射獲取構造函數對象並生成代理類實例 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()); } }
由此可見,動態代理真正的關鍵是在 getProxyClass 方法,該方法負責為一組接口動態地生成代理類類型對象。在該方法內部,您將能看到 Proxy 內的各路英雄(靜態變量)悉數登場。該方法總共可以分為四個步驟:
1、對這組接口進行一定程度的安全檢查,包括檢查接口類對象是否對類裝載器可見並且與類裝載器所能識別的接口類對象是完全相同的,還會檢查確保是 interface 類型而不是 class 類型。這個步驟通過一個循環來完成,檢查通過后將會得到一個包含所有接口名稱的字符串數組,記為 String[] interfaceNames
。總體上這部分實現比較直觀,所以略去大部分代碼,僅保留留如何判斷某類或接口是否對特定類裝載器可見的相關代碼。
清單 8. 通過 Class.forName 方法判接口的可見性
try { // 指定接口名字、類裝載器對象,同時制定 initializeBoolean 為 false 表示無須初始化類 // 如果方法返回正常這表示可見,否則會拋出 ClassNotFoundException 異常表示不可見 interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { }
2、從 loaderToCache 映射表中獲取以類裝載器對象為關鍵字所對應的緩存表,如果不存在就創建一個新的緩存表並更新到 loaderToCache。緩存表是一個 HashMap 實例,正常情況下它將存放鍵值對(接口名字列表,動態生成的代理類的類對象引用)。當代理類正在被創建時它會臨時保存(接口名字列表,pendingGenerationMarker)。標記 pendingGenerationMarke 的作用是通知后續的同類請求(接口數組相同且組內接口排列順序也相同)代理類正在被創建,請保持等待直至創建完成。
清單 9. 緩存表的使用
do { // 以接口名字列表作為關鍵字獲得對應 cache 值 Object value = cache.get(key); if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } if (proxyClass != null) { // 如果已經創建,直接返回 return proxyClass; } else if (value == pendingGenerationMarker) { // 代理類正在被創建,保持等待 try { cache.wait(); } catch (InterruptedException e) { } // 等待被喚醒,繼續循環並通過二次檢查以確保創建完成,否則重新等待 continue; } else { // 標記代理類正在被創建 cache.put(key, pendingGenerationMarker); // break 跳出循環已進入創建過程 break; } while (true);
3、動態創建代理類的類對象。首先是確定代理類所在的包,其原則如前所述,如果都為 public 接口,則包名為空字符串表示頂層包;如果所有非 public 接口都在同一個包,則包名與這些接口的包名相同;如果有多個非 public 接口且不同包,則拋異常終止代理類的生成。確定了包后,就開始生成代理類的類名,同樣如前所述按格式“$ProxyN”生成。類名也確定了,接下來就是見證奇跡的發生 —— 動態生成代理類:
清單 10. 動態生成代理類
// 動態地生成代理類的字節碼數組 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { // 動態地定義新生成的代理類 proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } // 把生成的代理類的類對象記錄進 proxyClasses 表 proxyClasses.put(proxyClass, null);
由此可見,所有的代碼生成的工作都由神秘的 ProxyGenerator 所完成了,當你嘗試去探索這個類時,你所能獲得的信息僅僅是它位於並未公開的 sun.misc 包,有若干常量、變量和方法以完成這個神奇的代碼生成的過程,但是 sun 並沒有提供源代碼以供研讀。至於動態類的定義,則由 Proxy 的 native 靜態方法 defineClass0 執行。
代碼生成過程進入結尾部分,根據結果更新緩存表,如果成功則將代理類的類對象引用更新進緩存表,否則清楚緩存表中對應關鍵值,最后喚醒所有可能的正在等待的線程。
走完了以上四個步驟后,至此,所有的代理類生成細節都已介紹完畢,剩下的靜態方法如 getInvocationHandler 和 isProxyClass 就顯得如此的直觀,只需通過查詢相關變量就可以完成,所以對其的代碼分析就省略了。
代理類實現推演:
分析了 Proxy 類的源代碼,相信對 Java 動態代理機制形成一個更加清晰的理解,整理一下思緒,一起來完成一次完整的推演過程吧。
清單 11. 代理類中方法調用的分派轉發推演實現
// 假設需代理接口 Simulator public interface Simulator { short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB; } // 假設代理類為 SimulatorProxy, 其類聲明將如下 final public class SimulatorProxy implements Simulator { // 調用處理器對象的引用 protected InvocationHandler handler; // 以調用處理器為參數的構造函數 public SimulatorProxy(InvocationHandler handler){ this.handler = handler; } // 實現接口方法 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(略) } // 第二步是調用 handler 的 invoke 方法分派轉發方法調用 Object r = null; try { r = handler.invoke(this, method, // 對於原始類型參數需要進行裝箱操作 new Object[] {new Integer(arg1), new Long(arg2), arg3}); }catch(Throwable e) { // 異常處理 2(略) } // 第三步是返回結果(返回類型是原始類型則需要進行拆箱操作) return ((Short)r).shortValue(); } }
模擬推演為了突出通用邏輯所以更多地關注正常流程,而淡化了錯誤處理,但在實際中錯誤處理同樣非常重要。從以上的推演中我們可以得出一個非常通用的結構化流程:第一步從代理接口獲取被調用的方法對象,第二步分派方法到調用處理器執行,第三步返回結果。在這之中,所有的信息都是可以已知的,比如接口名、方法名、參數類型、返回類型以及所需的裝箱和拆箱操作。
接下來讓我們把注意力重新回到先前被淡化的錯誤處理上來。在異常處理 1 處,由於我們有理由確保所有的信息如接口名、方法名和參數類型都准確無誤,所以這部分異常發生的概率基本為零,所以基本可以忽略。而異常處理 2 處,我們需要思考得更多一些。回想一下,接口方法可能聲明支持一個異常列表,而調用處理器 invoke 方法又可能拋出與接口方法不支持的異常,再回想一下先前提及的 Java 動態代理的關於異常處理的特點,對於不支持的異常,必須拋 UndeclaredThrowableException 運行時異常。所以通過再次推演,我們可以得出一個更加清晰的異常處理 2 的情況:
清單 12. 細化的異常處理 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學習筆記系列: