1. 程式人生 > >Java - Class物件、反射、動態代理學習

Java - Class物件、反射、動態代理學習

         Java是一個動態連結語言。Java中的類在需要時才會被載入,這個類我們可以視為一個Class物件(xxx.class)。管理這些Class物件的類則就是Class類。

         這裡有點拗口,對於初學者來說(比如:筆者),需要仔細的理解一下。我們知道.java檔案在編譯之後會生成一個.class檔案。比如Student.java,該檔案裡存放著Student類的定義。編譯之後會生成一個Student.class檔案。在JVM中,可以簡單的理解Student.class就是一個Class物件(不是指那個檔案,而是JVM中的一個數據物件)。它管理著Student這個類的全部資訊。

         我們直接看Class的原始碼。在 java.lang中。本文主要介紹幾個常用的方法,以及它們使用的使用方法。

         首先是構造器:Class的構造器是私有化的,私有化構造器一般用於禁止使用者直接進行建立物件。

    /**
     * 構造器私有化,引數為類載入器
     * 禁止外界直接進行建立物件
     * */
    private Class(ClassLoader loader) {
        classLoader = loader;
    }

那麼Class物件該怎麼建立呢?

       在解答這個問題之前,我們先看一個靜態方法:Class.forName(String s);用Java連線過資料庫的同學應該對此並不陌生。遺憾的是,該方法的底層實現使用native方法 forName0實現的。forName0的引數有四個:String name, boolean initialize,ClassLoader loader, Class<?> caller。其中ClassLoader是類載入器,用於載入一個類。

       其使用方法和功能如下:

    /*******************************************************
     *  得到類名為className的一個Class物件的引用 xxx.class
     *  如果找不到會丟擲異常 ClassNotFoundException
     *
     *  呼叫該方法的主要目的是,className.class 如果沒有被載入,
     *  則會被載入,從而執行className.class的靜態子句等。
     *
     *  Class c = Class.forName("java.util.Random");
     *
     *  String driver = "oracle.jdbc.driver.OracleDriver"; //orale 驅動
     *  Class.forName(driver);                              //註冊驅動,使用類載入
     ******************************************************/  
    @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

   private static native Class<?> forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class<?> caller)
   throws ClassNotFoundException;

 

      現在我們來解答一下,如何對Class進行例項化,得到一個xxxx.class。

     1、通過靜態方法.Class.forName(String s) 適用於知道類名(People)的情況:

      Class c= Class.forName("People");  // c就是 People.class
 

     2,我們還可以通過物件來獲得Class物件,適用於知道例項物件(people)的情況。

     Class c=people.getClass();

     getClass(),方法是Object類中的方法,可以返回people的Class物件。

 

通過Class物件得到一個例項

    得到Class物件之後,我們就可以使用Class中的成員方法了。而不是隻能使用靜態方法。

     還有另一個常用的方法。叫newInstance();該方法,是用來得到一個Class物件(比如上面的People.class)的一個例項。

     People people = c.newInstance();

     通常情況下,我們會寫成      Class.forName("People").newInstance();

 

Class物件的屬性

     看到這裡,我們應該對Class物件有了一定的瞭解。下面我們來看看Class物件的屬性。

     Class物件是用於管理類資訊的,包括類的方法,屬性,型別,名稱,載入器等等。(比如String.class 管理String類的全部資訊)。比如c.getClassLoader() ,就可以得到People的類載入器。

     Class物件的存在,使得我們可以訪問類資訊。

 

反射:由物件得到類屬性和方法資訊。

      Class類和java.lang.reflect類庫一起提供了反射的支援。reflect中包含了三個重要的類:Field,Method,Constructor。見名知義,分別管理欄位,方法,構造器。

     Class的getMethods()以及getConstructor()方法,分別返回Method物件陣列和Constructor物件陣列。這兩個類,都提供了深層方法,來解析物件所代表的方法、並獲取其名字、引數、返回值等資訊。

      這是我們下面動態代理能實現的基礎。通過物件obj 得到Object.class,再得到Object的各個方法Method。

 

動態代理

     動態代理:產生一個物件obj的代理物件Proxy。代理物件存在的價值主要在於攔截對真實業務物件obj的訪問。(代理物件:經紀人,業務物件:明星)。代理物件應該具有和業務物件相同的方法。最後真正執行的,依然是業務物件的方法。

     在這裡需要介紹一下Proxy類的一個靜態方法

  public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
    throws IllegalArgumentException

     用於生成一個代理例項。

/******************************************
 *           代理類:
 * 用於生成一個代理物件,通常使用靜態方法:Proxy.newProxyInstance( , , )。共有三個引數:
 * ClassLoader loader用來指明生成代理物件使用哪個類裝載器,
 * Class<?>[] interfaces用來指明生成哪個物件(介面)的代理物件,通過介面指定,
 * InvocationHandler h用來指明產生的這個代理物件要做什麼事情。
 * 所以我們只需要呼叫newProxyInstance方法就可以得到某一個物件的代理物件了。
 *****************************************/
public class Proxy implements java.io.Serializable {
     ...
     public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException{
        ...
     }
     ...
}

      這裡又有一個新的型別,InvocationHandler h ,該型別是一個介面。裡面只有一個方法invoke()。動態代理實現invoke()用於通知業務物件進行執行相應的操作。    

    下面,我們用一個例子來說明動態代理具體是怎麼實現的。

1、先規定一個介面,代理想要做的操作

package proxy;
/**
 * @ClassName: Subscribe
 * @Description: 邀約
 * @author: 西瓜shine
 * @date: 2018-12-28 下午7:19:02
 *
 */
public interface Subscribe {
    void confirmTime(String s);
    void confirmPlace(String s);
}

  2、業務物件:老闆

package proxy;
/**
 * @ClassName: Boss
 * @Description: 老闆,業務物件類。
 * @author: 西瓜shine
 * @date: 2018-12-28 下午7:19:02
 */
public class Boss implements Subscribe{
    private String name;
    public Boss(String name) {
        this.name =name;
    }

    @Override
    public void confirmTime(String s) {
        System.out.println("Boss:Time is no problem:"+s);
    }

    @Override
    public void confirmPlace(String s) {
        System.out.println("Boss:Place is no problem:"+s);
    }
}

3、代理物件:祕書 

     這裡需要實現一個介面InvocationHandler中的invoke()。引數和作用,在上文已經見過。

     該方法內可以對請求進行初步處理,通過判斷客戶端發來的請求方法(一個代理可以實現多個方法)method的名稱(method.getName)來判斷,可在呼叫業務物件的方法之前,進行簡單的處理。之後呼叫業務物件的介面方法來實現

具體的操作:   method.invoke(boss,args); 該語句的意思是:呼叫boss的method方法,引數為args。

package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @ClassName: Secretary
 * @Description: 這個代理類負責生成Boss物件的代理:祕書
 * @author: 西瓜shine
 * @date: 2018-12-28 下午7:19:02
 *
 */
public class Secretary implements InvocationHandler {
    private Boss boss ;              // 每個祕書只有一個老闆
    private String name;
    public Secretary(String name,String boss) {
        this.name = name;
        this.boss = new Boss(boss);
    }

    public Secretary(String boss) {
        this.name = "Secretary of"+boss;
        this.boss = new Boss(boss);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("confirmTime"))
            System.out.println("Secretary: Just a minute,I will ask my boss ,Is the time ok?");
        if(method.getName().equals("confirmPlace"))
            System.out.println("Secretary: Just a minute,I will ask my boss, Is the place ok?");
        return method.invoke(boss,args);
    }

}

 4、測試。

package proxy;

import java.lang.reflect.Proxy;
/**
 * @ClassName:  Interview
 * @Description: 模擬訪客先老闆預約,只知道老闆的姓名“Ma Yun”
 * @author: 西瓜shine
 * @date: 2018-12-28 下午7:19:02
 *
 */
public class Interview {
    public static void require(Subscribe proxy){
        proxy.confirmTime("tomorrow");
        proxy.confirmPlace("meeting room");
    }

    public static void main(String[] args){
        Secretary secretary = new Secretary("Ma Yun");   // 得到一個馬雲的祕書的聯絡方式
        Subscribe proxy = (Subscribe) Proxy.newProxyInstance(  // 得到預約的代理
                Subscribe.class.getClassLoader(),
                new Class[] {Subscribe.class},
                secretary
        );
        require(proxy);
    }
}

執行結果:

Secretary: Just a minute,I will ask my boss ,Is the time ok?
Boss:Time is no problem:tomorrow
Secretary: Just a minute,I will ask my boss, Is the place ok?
Boss:Place is no problem:meeting room

 

此時執行已經正確。

但是,獲取代理的方法

Subscribe proxy = (Subscribe) Proxy.newProxyInstance(  // 得到預約的代理
                Subscribe.class.getClassLoader(),
                new Class[] {Subscribe.class},
                secretary
        );

放在客戶端有些不太符合面向物件設計,而且每個客戶都要重複該程式碼。所以對Subscribe類和客戶端進行的修改。

public class Secretary implements InvocationHandler {
    private Boss boss ;              // 每個祕書只有一個老闆。實際
    private String name;
    public Secretary(String name,String boss) {
        this.name = name;
        this.boss = new Boss(boss);
    }

    public Secretary(String boss) {
        this.name = "Secretary of"+boss;
        this.boss = new Boss(boss);
    }

    public Subscribe getProxy(){
        return (Subscribe) Proxy.newProxyInstance(  // 得到預約的代理
                Subscribe.class.getClassLoader(),
                new Class[] {Subscribe.class},
                this
        );
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("confirmTime"))
            System.out.println("Secretary: Just a minute,I will ask my boss ,Is the time ok?");
        if(method.getName().equals("confirmPlace"))
            System.out.println("Secretary: Just a minute,I will ask my boss, Is the place ok?");
        return method.invoke(boss,args);
    }
}
package proxy;

/**
 * @ClassName:  Interview
 * @Description: 模擬訪客先老闆預約,只知道老闆的姓名“Ma Yun”
 * @author: 西瓜shine
 * @date: 2018-12-28 下午7:19:02
 *
 */
public class Interview {
    public static void require(Subscribe proxy){
        proxy.confirmTime("tomorrow");
        proxy.confirmPlace("meeting room");
    }

    public static void main(String[] args){
        Secretary secretary = new Secretary("Ma Yun");   // 得到一個馬雲的祕書的聯絡方式
        Subscribe proxy = secretary.getProxy();                // 從祕書那邊得到一個邀約方法(可視為邀約信的格式)
        require(proxy);                                        // 填寫邀約。
    }
}