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類型的屬性。

– getMethods():獲得類的public類型的方法。–getDeclaredFields():獲得類的所有屬性。

– 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.通過代理調用方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章