1. 程式人生 > >Java中的反射(reflection)和代理(proxy)

Java中的反射(reflection)和代理(proxy)

Java中的反射和代理

特別宣告:本文主要是記錄了學習反射和代理的學習過程,代理部分的大段文字均屬轉載。您可以閱讀原文除此之外,所有程式碼及反射部分均屬個人所寫。

反射

最近剛讀了一遍《Java程式設計思想》第四版中第14章——型別資訊,獲益匪淺,摘取了一部分內容。具體如下:

反射:執行時的類資訊
和C++體系中的RTTI相似,反射是在執行時獲取某個類的資訊。Class類與java.lang.reflect類庫一起對反射的概念進行了支援,該類包括FieldMethod以及Constructor類(每個類都實現了Member介面)。這些型別的物件都是由JVM在執行時建立的,用以表示未知類裡對應的成員。這樣你就可以使用Constructor

建立新的物件,用get()set()方法讀取和修改有關Field物件關聯的欄位,用invoke方法呼叫有關Method物件關聯的方法。另外,還可以呼叫getFields()getMethods()getConstructors()等很便利的方法以返回表示欄位、方法和構造器的物件的陣列

理解RTTI和反射的真正區別:
對RTTI來說,編譯器在編譯時開啟和檢查.class檔案。而對於反射機制來說,.class檔案在編譯時是不可獲取的,所以是在執行時開啟和檢查.class檔案。

示例如下:

//詳解見《Java程式設計思想》第四版
package me.ethan.reflect
; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.regex.Pattern; public class ShowMethods { private static String usage = "usage:\n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or:\n" + "ShowMethods qualified.class.name word\n"
+ "To search for methods involving 'word'"; private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if (args.length < 1) { System.out.println(usage); System.exit(0); } int lines = 0; try { //返回一個Class物件 Class<?> c = Class.forName(args[0]); Method[] methods = c.getMethods(); //返回一個Method陣列 Constructor[] constructors = c.getConstructors(); //返回一個Constructor陣列 if (args.length == 1) { for (Method method : methods) { System.out.println(p.matcher(method.toString()).replaceAll("")); } for (Constructor constructor : constructors) { System.out.println(p.matcher(constructor.toString()).replaceAll("")); } lines = methods.length + constructors.length; } else { for (Method method : methods) { if (method.toString().indexOf(args[1]) != -1) { System.out.println(p.matcher(method.toString()).replaceAll("")); lines++; } } for (Constructor constructor : constructors) { if (constructor.toString().indexOf(args[1]) != -1) { System.out.println(p.matcher(constructor.toString()).replaceAll("")); lines++; } } } } catch (ClassNotFoundException e) { System.out.println("No such Class"+e); } } }

輸出結果:

public static void main(String[])
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
public ShowMethods()    //這個是預設的無參構造器

執行上面的main()方法需要手動設定引數,引數為:包名+類名(即類的全限定名)

代理

舉個例子來說明代理的作用:假設我們想邀請一位明星,那麼並不是直接連線明星,而是聯絡明星的經紀人,來達到同樣的目的,明星就是一個目標物件,他只要負責活動中的節目,而其他瑣碎的事情就交給他的代理人(經紀人)來解決這就是代理思想在現實中的一個例子。

代理模式的關鍵點是:代理物件與目標物件.代理物件是對目標物件的擴充套件,並會呼叫目標物件。

靜態代理

靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類。
示例如下:

//公共介面(被代理類和代理類都要實現該介面)
package me.ethan.reflect;

public interface Image {
    void display();
}
//被代理類
package me.ethan.reflect;

public class RealImage implements Image {
    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }

    @Override
    public void display() {
        System.out.println("Displaying "+fileName);
    }

    public void loadFromDisk(String fileName) {
        System.out.println("Loading "+fileName);
    }
}
//代理類
package me.ethan.reflect;

public class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;

    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(fileName);
        }
        realImage.display();
    }
}
//測試類
package me.ethan.reflect;

public class Test {
    public static void main(String[] args) {
        Image image = new ProxyImage("test.jpg");
        //呼叫了被代理物件的loadFromDisk()方法,從磁碟載入
        image.display();

        System.out.println("");
        //未從磁碟載入
        image.display();
    }
}

輸出如下:

Loading test.jpg
Displaying test.jpg

Displaying test.jpg

靜態代理的優缺點:
優點:可以做到在不修改目標物件的功能前提下,對目標功能擴充套件。
缺點:因為代理物件需要與目標物件實現一樣的介面,所以會有很多代理類,類太多,同時,一旦介面增加方法,目標物件與代理物件都要維護。

為了應對靜態代理所帶來的不便,動態代理應運而生。

JDK動態代理

JDK動態代理有幾個特點:

  1. 代理物件,不需要實現介面
  2. 代理物件的生成,是利用JDK的API,動態的在記憶體中構建代理物件(需要我們指定建立代理物件/目標物件實現的介面的型別)
  3. JDK動態代理也叫做:JDK代理,介面代理

JDK中生成代理物件的API
JDK實現代理只需要使用newProxyInstance()方法,但是該方法需要接收三個引數,完整的寫法是:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
/*
    分別解釋一下上面的三個引數
    ClassLoader loader:很明顯,需要一個類載入器。寫法固定:
                被代理物件.getClass().getClassLoader();

    Class<?>[] interfaces:被代理物件實現的介面的型別,使用泛型方式確認型別。多種寫法:
                被代理物件.getClass().getInterfaces();
    或者
                Class[Interface1.class,Interface2.class,Interface3.class……]    

    InvocationHandler h:一個實現了InvocationHandler介面的類或者匿名內部類均可
*/

示例如下:

//動態代理類
package me.ethan.reflect;

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

public class ProxyFactory {
    //維護一個Object物件(意味著可以轉為任意的物件)
    private Object image;

    public ProxyFactory(Object image) {
        this.image = image;
    }

    //JDK動態代理
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                image.getClass().getClassLoader(),
                image.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("開始事務……");
                        //執行被代理物件方法
                        Object obj = method.invoke(image, args);
                        System.out.println("提交事務……");
                        return obj;
                    }
                });
    }
}

//測試類
package me.ethan.reflect;

public class Test {
    public static void main(String[] args) {
        //被代理物件
        Image image = new RealImage("test.jpg");
        System.out.println(image.getClass().getSimpleName());    //型別為RealImage,而不是Image

        System.out.println("");
        //建立代理物件
        Image proxyImage = (Image) new ProxyFactory(image).getProxyInstance();
        System.out.println(proxyImage.getClass().getCanonicalName());   //記憶體中的動態代理物件 com.sun.proxy.$Proxy0
        proxyImage.display();
    }
}

輸出如下:

Loading test.jpg
RealImage

com.sun.proxy.$Proxy0
開始事務……
Displaying test.jpg
提交事務……

缺點:代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理

除上述JDK代理之外,還有不是基於介面的Cglib動態代理。

Cglib動態代理

靜態代理和動態代理模式都是要求目標物件是實現一個介面的目標物件,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式實現代理,這種方法就叫做:Cglib動態代理。
Cglib代理,也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件。

  • JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現
  • Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面,它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
  • Cglib包的底層是通過使用一個小而快的位元組碼處理框架ASM來轉換位元組碼並生成新的類,不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉

Cglib子類代理實現方法:

  1. 需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接引入spring-core:4.3.13.RELEASE即可
  2. 引入功能包後,就可以在記憶體中動態構建子類
  3. 代理的類不能為final,否則報錯
  4. 目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法。

示例如下:
匯入spring-core:4.3.13.RELEASE

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.3.13.RELEASE</version>
    </dependency>

主要程式碼:

//建立Cglib動態代理類
package me.ethan.reflect;

import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxyFactory implements MethodInterceptor {
    //維護被代理物件
    public Object image;

    public CglibProxyFactory(Object image) {
        this.image = image;
    }

    //為被代理物件建立一個代理物件
    public Object getProxyInstance() {
        //工具類
        Enhancer enhancer = new Enhancer();
        //設定父類
        enhancer.setSuperclass(image.getClass());
        //設定回撥函式
        enhancer.setCallback((Callback) this);
        //建立子類(代理物件)
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("開始事務……");
        //執行目標方法
        Object obj = method.invoke(image, objects);
        System.out.println("提交事務……");
        return obj;
    }
}

//測試類
package me.ethan.reflect;

public class Test {
    public static void main(String[] args) {
        //建立目標物件(假設沒有實現任何介面)
        RealImage realImage = new RealImage("test.jpg");
        //建立代理物件
        RealImage proxyImage = (RealImage) new CglibProxyFactory(realImage).getProxyInstance();
        //執行代理物件的方法
        proxyImage.display();
    }
}

輸出如下:

Loading test.jpg
開始事務……
Displaying test.jpg
提交事務……

在練習使用Cglib對目標物件進行動態代理時,出現了一個小插曲:首次執行時,丟擲異常:

Exception in thread "main" java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given

原因:使用Cglib動態代理時,要求目標類必須要有一個無參構造器。

新增無參構造器後執行正常。