1. 程式人生 > >Java程式設計師從笨鳥到菜鳥之(八)反射和代理機制

Java程式設計師從笨鳥到菜鳥之(八)反射和代理機制

   反射和代理機制是JDK5.0提供的java新特性,反射的出現打破了java一些常規的規則,如,私有變數不可訪問。但反射和代理在學習過程中也是一個比較難理解的知識點。本人曾經學過一段時間的反射和代理,但好長時間不用好像有點生疏了,當時學的時候就理解的不是很透徹,這次總結算是重新學習一遍吧,如果有什麼錯誤,請大家拍磚:

 先看一下,Java 反射機制主要提供了以下功能:

•在執行時判斷任意一個物件所屬的類。

•在執行時構造任意一個類的物件。

•在執行時判斷任意一個類所具有的成員變數和方法。

•在執行時呼叫任意一個物件的方法

     一般而言,開發者社群說到動態語言,大致認同的一個定義是:“程式執行時,允許改變程式結構或變數型別,這種語言稱為動態語言”儘管在這樣的定義與分類下Java不是動態語言,它卻有著一個非常突出的動態相關機制:Reflection。這個字的意思是“反射、映象、倒影”,用在Java身上指的是我們可以於執行時載入、探知、使用編譯期間完全未知的classes。換句話說,Java程式可以載入一個執行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其物件實體、或對其fields設值、或喚起其methods。這種“看透class”的能力(the ability of the program to examine itself)被稱為introspection(內省、內觀、反省)。Reflection和introspection是常被並提的兩個術語

API簡介

     在JDK中,主要由以下類來實現Java反射機制,這些類都位於java.lang.reflect包中

–Class類:代表一個類。

–Field 類:代表類的成員變數(成員變數也稱為類的屬性)。

–Method類:代表類的方法。

–Constructor 類:代表類的構造方法。

–Array類:提供了動態建立陣列,以及訪問陣列的元素的靜態方法

       在java.lang.Object類中定義了getClass()方法,因此對於任意一個Java物件,都可以通過此方法獲得物件的型別。

Class類是Reflection API 中的核心類,它有以下方法

–getName():獲得類的完整名字。

–getFields():獲得類的public型別的屬性。

–getDeclaredFields():獲得類的所有屬性。

–getMethods():獲得類的public型別的方法。

–getDeclaredMethods():獲得類的所有方法。

-getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name引數指定方法的名字,parameterTypes引數指定方法的引數型別。

-getConstructors():獲得類的public型別的構造方法。

•getConstructor(Class[] parameterTypes):獲得類的特定構造方法,parameterTypes引數指定構造方法的引數型別。

•newInstance():通過類的不帶引數的構造方法建立這個類的一個物件。

•(2)通過預設構造方法建立一個新物件:

•Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});

•以上程式碼先呼叫Class類的getConstructor()方法獲得一個Constructor 物件,它代表預設的構造方法,然後呼叫Constructor物件的newInstance()方法構造一個例項。

•(3)獲得物件的所有屬性:

•Field fields[]=classType.getDeclaredFields();

•Class 類的getDeclaredFields()方法返回類的所有屬性,包括public、protected、預設和private訪問級別的屬性

(4)Method類的invoke(Object obj,Object args[])方法接收的引數必須為物件,如果引數為基本型別資料,必須轉換為相應的包裝型別的物件。invoke()方法的返回值總是物件,如果實際被呼叫的方法的返回型別是基本型別資料,那麼invoke()方法會把它轉換為相應的包裝型別的物件,再將其返回

(5)Java中,無論生成某個類的多少個物件,這些物件都會對應於同一個Class物件。 要想使用反射,首先需要獲得待處理類或物件所對應的Class物件。 

獲取某個類或某個物件所對應的Class物件的常用的3種方式: 

a) 使用Class類的靜態方法forName:Class.forName(“java.lang.String”); 

b) 使用類的.class語法:String.class; 

c) 使用物件的getClass()方法:String s = “aa”; Class<?> clazz = s.getClass(); 

下面寫一個程式來用一下這些API吧:

//獲得MethodInvoke類對應的一個clas物件Class<?> MethodInvok=MethodInvoke.class;//獲得一個MethodInvoke類對應的物件例項Object MethodInvo=MethodInvok.newInstance();//獲得MethodInvo物件對應的add方法對應的一個物件例項1):Method method=MethodInvok.getMethod("add", int.class,int.class);//呼叫MethodInvo物件對應的add方法對應的一個物件(MethodInvo)例項所代表的方法,並獲得結果  2)Object result= method.invoke(MethodInvo, 1,2);  System.out.println(result);  System.out.println("--------------------------------------");   Method method1=MethodInvok.getMethod("print",String.class);   Object Result1=method1.invoke(MethodInvo, "tom");  System.out.println(Result1);

注:1)處的int.class,int.class可以寫為new Class[]{int.class,int.class}

原因在於getMethod方法的第二個引數是一個可變引數。

2)處的1,2可以寫為new int【】{1,2},原因如1);

4.若想通過類的不帶引數的構造方法來生成物件,我們有兩種方式: 

a) 先獲得Class物件,然後通過該Class物件的newInstance()方法直接生成即可: 

Class<?> classType = String.class; 

Object obj = classType.newInstance(); 

b) 先獲得Class物件,然後通過該物件獲得對應的Constructor物件,再通過該Constructor物件的newInstance()方法生成: 

Class<?> classType = Customer.class; 

Constructor cons = classType.getConstructor(new Class[]{}); 

Object obj = cons.newInstance(new Object[]{}); 

注:

4. 若想通過類的帶引數的構造方法生成物件,只能使用下面這一種方式: 

Class<?> classType = Customer.class; 

Constructor cons = classType.getConstructor(new Class[]{String.class, int.class}); 

Object obj = cons.newInstance(new Object[]{“hello”, 3}); 

 程式碼示例:

     // 該方法實現對Customer物件的拷貝操作 public Object copy(Object object) throws Exception {  Class<?> classType = object.getClass();        //先呼叫Class類的getConstructor()方法獲得一個Constructor 物件,它代表預設的構造方法,然後呼叫        Constructor物件的newInstance()方法構造一個例項。  Object objectCopy = classType.getConstructor(new Class[] {})    .newInstance(new Object[] {});  // Class 類的getDeclaredFields()方法返回類的所有屬性,包括public、protected、默        認和private訪問級別的屬性  Field[] fields = classType.getDeclaredFields();  for (Field field : fields)  {   String name = field.getName();         // 將屬性的首字母轉換為大寫   String firstLetter = name.substring(0, 1).toUpperCase();   String getMethodName = "get" + firstLetter + name.substring(1);   String setMethodName = "set" + firstLetter + name.substring(1);   Method getMethod = classType.getMethod(getMethodName,     new Class[] {});   Method setMethod = classType.getMethod(setMethodName,     new Class[] { field.getType() });   Object value = getMethod.invoke(object, new Object[] {});   setMethod.invoke(objectCopy, new Object[] { value });  }  // 以上兩行程式碼等價於下面一行  // Object obj2 = classType.newInstance();  // System.out.println(obj);  return objectCopy; }

5.Integer.TYPE返回的是int,而Integer.class返回的是Integer類所對應的Class物件。 

java.lang.Array 類提供了動態建立和訪問陣列元素的各種靜態方法

一維陣列的簡單建立,設值,取值

Object array = Array.newInstance(classType, 10);

Array.set(array, 5, "hello");

String str = (String)Array.get(array, 5);

二維陣列的簡單建立,設值,取值

       //建立一個設值陣列維度的陣列       int[] dims = new int[] { 5, 10, 15 };      //利用Array.newInstance建立一個數組物件,第一個引數指定陣列的型別,第      二個引數設值陣列的維度,下面是建立一個長寬高為:5,10,15的三維陣列  Object array = Array.newInstance(Integer.TYPE, dims);  System.out.println(array instanceof int[][][]);       //獲取三維陣列的索引為3的一個二維陣列  Object arrayObj = Array.get(array, 3);       //獲取二維陣列的索引為5的一個一維陣列  arrayObj = Array.get(arrayObj, 5);      //設值一維陣列arrayObj下標為10的值設為37  Array.setInt(arrayObj, 10, 37);  int[][][] arrayCast = (int[][][]) array;  System.out.println(arrayCast[3][5][10]);

利用反射訪問類的私有方法:

程式碼示例:

Private p = new Private();Class<?> classType = p.getClass();Method method = classType.getDeclaredMethod("sayHello",new Class[] { String.class }); method.setAccessible(true);//壓制Java的訪問控制檢查,使允許訪問private方法String str = (String)method.invoke(p, new Object[]{"zhangsan"});System.out.println(str);

利用反射訪問類的私有變數:

    Private2 p = new Private2();   Class<?> classType = p.getClass();   Field field = classType.getDeclaredField("name");   field.setAccessible(true);//壓制Java對訪問修飾符的檢查   field.set(p, "lisi");   System.out.println(p.getName());

                                                                               代理

代理模式的作用是:為其他物件提供一種代理以控制對這個物件的訪問。

•在某些情況下,一個客戶不想或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用

代理模式一般涉及到的角色有

–抽象角色:宣告真實物件和代理物件的共同介面

–代理角色:代理物件角色內部含有對真實物件的引用,從而可以操作真實物件,同時代理物件提供與真實物件相同的介面以便在任何時刻都能代替真實物件。同時,代理物件可以在執行真實物件操作時,附加其他的操作,相當於對真實物件進行封裝

–真實角色:代理角色所代表的真實物件,是我們最終要引用的物件

Java動態代理類位於java.lang.reflect包下,一般主要涉及到以下兩個類:

•(1)Interface InvocationHandler:該介面中僅定義了一個方法

–public object invoke(Object obj,Method method, Object[] args)

•在實際使用時,第一個引數obj一般是指代理類,method是被代理的方法,如上例中的request(),args為該方法的引數陣列。這個抽象方法在代理類中動態實現。

•(2)Proxy:該類即為動態代理類,作用類似於上例中的ProxySubject,其中主要包含以下內容

•protected Proxy(InvocationHandler h):建構函式,用於給內部的h賦值。

•static Class getProxyClass (ClassLoader loader, Class[] interfaces):獲得一個代理類,其中loader是類裝載器,interfaces是真實類所擁有的全部介面的陣列。

•static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理類的一個例項,返回後的代理類可以當作被代理類使用(可使用被代理類的在Subject介面中宣告過的方法)

所謂Dynamic Proxy(動態代理)是這樣一種class:它是在執行時生成的class,在生成它時你必須提供一組interface給它,然後該class就宣稱它實現了這些interface。你當然可以把該class的例項當作這些interface中的任何一個來用。當然,這個Dynamic Proxy其實就是一個Proxy,它不會替你作實質性的工作,在生成它的例項時你必須提供一個handler,由它接管實際的工作

在使用動態代理類時,我們必須實現InvocationHandler介面

通過代理的方式,被代理的物件(RealSubject)可以在執行時動態改變,需要控制的介面(Subject介面)可以在執行時改變,控制的方式(DynamicSubject類)也可以動態改變,從而實現了非常靈活的動態代理關係

動態代理是指客戶通過代理類來呼叫其它物件的方法

•動態代理使用場合:

   •除錯

   •遠端方法呼叫(RMI)

動態代理的步驟:

1.建立一個實現介面InvocationHandler的類,它必須實現invoke方法

2.建立被代理的類以及介面

3.通過Proxy的靜態方法

newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 建立一個代理

4.通過代理呼叫方法

下一篇寫: