深入理解 Java 反射和動(dòng)態(tài)代理

簡介

什么是反射

反射(Reflection)是 Java 程序開發(fā)語言的特征之一,它允許運(yùn)行中的 Java 程序獲取自身的信息梭姓,并且可以操作類或?qū)ο蟮膬?nèi)部屬性。

通過反射機(jī)制斗塘,可以在運(yùn)行時(shí)訪問 Java 對象的屬性欣舵,方法,構(gòu)造方法等揭鳞。

反射的應(yīng)用場景

反射的主要應(yīng)用場景有:

  • 開發(fā)通用框架 - 反射最重要的用途就是開發(fā)各種通用框架炕贵。很多框架(比如 Spring)都是配置化的(比如通過 XML 文件配置 JavaBean、Filter 等)野崇,為了保證框架的通用性称开,它們可能需要根據(jù)配置文件加載不同的對象或類,調(diào)用不同的方法乓梨,這個(gè)時(shí)候就必須用到反射——運(yùn)行時(shí)動(dòng)態(tài)加載需要加載的對象鳖轰。

  • 動(dòng)態(tài)代理 - 在切面編程(AOP)中,需要攔截特定的方法扶镀,通常蕴侣,會(huì)選擇動(dòng)態(tài)代理方式。這時(shí)臭觉,就需要反射技術(shù)來實(shí)現(xiàn)了昆雀。

  • 注解 - 注解本身僅僅是起到標(biāo)記作用辱志,它需要利用反射機(jī)制,根據(jù)注解標(biāo)記去調(diào)用注解解釋器狞膘,執(zhí)行行為揩懒。如果沒有反射機(jī)制,注解并不比注釋更有用客冈。

  • 可擴(kuò)展××× - 應(yīng)用程序可以通過使用完全限定名稱創(chuàng)建可擴(kuò)展性對象實(shí)例來使用外部的用戶定義類旭从。

反射的缺點(diǎn)

  • 性能開銷 - 由于反射涉及動(dòng)態(tài)解析的類型,因此無法執(zhí)行某些 Java 虛擬機(jī)優(yōu)化场仲。因此和悦,反射操作的性能要比非反射操作的性能要差,應(yīng)該在性能敏感的應(yīng)用程序中頻繁調(diào)用的代碼段中避免渠缕。

  • 破壞封裝性 - 反射調(diào)用方法時(shí)可以忽略權(quán)限檢查鸽素,因此可能會(huì)破壞封裝性而導(dǎo)致安全問題。

  • 內(nèi)部曝光 - 由于反射允許代碼執(zhí)行在非反射代碼中非法的操作亦鳞,例如訪問私有字段和方法馍忽,所以反射的使用可能會(huì)導(dǎo)致意想不到的副作用,這可能會(huì)導(dǎo)致代碼功能失常并可能破壞可移植性燕差。反射代碼打破了抽象遭笋,因此可能會(huì)隨著平臺(tái)的升級(jí)而改變行為。

反射機(jī)制

類加載過程

image

image.png

類加載的完整過程如下:

(1)在編譯時(shí)徒探,Java 編譯器編譯好 .java 文件之后瓦呼,在磁盤中產(chǎn)生 .class 文件。.class 文件是二進(jìn)制文件测暗,內(nèi)容是只有 JVM 能夠識(shí)別的機(jī)器碼央串。

(2)JVM 中的類加載器讀取字節(jié)碼文件,取出二進(jìn)制數(shù)據(jù)碗啄,加載到內(nèi)存中质和,解析.class 文件內(nèi)的信息。類加載器會(huì)根據(jù)類的全限定名來獲取此類的二進(jìn)制字節(jié)流稚字;然后饲宿,將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu);接著胆描,在內(nèi)存中生成代表這個(gè)類的 java.lang.Class 對象褒傅。

(3)加載結(jié)束后,JVM 開始進(jìn)行連接階段(包含驗(yàn)證袄友、準(zhǔn)備殿托、初始化)。經(jīng)過這一系列操作剧蚣,類的變量會(huì)被初始化支竹。

Class 對象

要想使用反射旋廷,首先需要獲得待操作的類所對應(yīng)的 Class 對象。Java 中礼搁,無論生成某個(gè)類的多少個(gè)對象饶碘,這些對象都會(huì)對應(yīng)于同一個(gè) Class 對象。這個(gè) Class 對象是由 JVM 生成的馒吴,通過它能夠獲悉整個(gè)類的結(jié)構(gòu)扎运。所以,java.lang.Class 可以視為所有反射 API 的入口點(diǎn)饮戳。

反射的本質(zhì)就是:在運(yùn)行時(shí)豪治,把 Java 類中的各種成分映射成一個(gè)個(gè)的 Java 對象。

舉例來說扯罐,假如定義了以下代碼:

User user = new User();

步驟說明:

  1. JVM 加載方法的時(shí)候负拟,遇到 new User(),JVM 會(huì)根據(jù) User 的全限定名去加載 User.class 歹河。

  2. JVM 會(huì)去本地磁盤查找 User.class 文件并加載 JVM 內(nèi)存中掩浙。

  3. JVM 通過調(diào)用類加載器自動(dòng)創(chuàng)建這個(gè)類對應(yīng)的 Class 對象,并且存儲(chǔ)在 JVM 的方法區(qū)秸歧。注意:一個(gè)類有且只有一個(gè) Class 對象厨姚。

使用反射

java.lang.reflect 包

Java 中的 java.lang.reflect 包提供了反射功能。java.lang.reflect 包中的類都沒有 public 構(gòu)造方法键菱。

java.lang.reflect 包的核心接口和類如下:

  • Member 接口 - 反映關(guān)于單個(gè)成員(字段或方法)或構(gòu)造函數(shù)的標(biāo)識(shí)信息遣蚀。

  • Field 類 - 提供一個(gè)類的域的信息以及訪問類的域的接口。

  • Method 類 - 提供一個(gè)類的方法的信息以及訪問類的方法的接口纱耻。

  • Constructor 類 - 提供一個(gè)類的構(gòu)造函數(shù)的信息以及訪問類的構(gòu)造函數(shù)的接口。

  • Array 類 - 該類提供動(dòng)態(tài)地生成和訪問 JAVA 數(shù)組的方法险耀。

  • Modifier 類 - 提供了 static 方法和常量弄喘,對類和成員訪問修飾符進(jìn)行解碼。

  • Proxy 類 - 提供動(dòng)態(tài)地生成代理類和類實(shí)例的靜態(tài)方法甩牺。

獲得 Class 對象

獲得 Class 的三種方法:

(1)使用 Class 類的 forName 靜態(tài)方法

示例:

package io.github.dunwu.javacore.reflect;public class ReflectClassDemo01 {    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("io.github.dunwu.javacore.reflect.ReflectClassDemo01");
        System.out.println(c1.getCanonicalName());

        Class c2 = Class.forName("[D");
        System.out.println(c2.getCanonicalName());

        Class c3 = Class.forName("[[Ljava.lang.String;");
        System.out.println(c3.getCanonicalName());
    }
}//Output://io.github.dunwu.javacore.reflect.ReflectClassDemo01//double[]//java.lang.String[][]

使用類的完全限定名來反射對象的類蘑志。常見的應(yīng)用場景為:在 JDBC 開發(fā)中常用此方法加載數(shù)據(jù)庫驅(qū)動(dòng)。

(2)直接獲取某一個(gè)對象的 class

示例:

public class ReflectClassDemo02 {    public static void main(String[] args) {        boolean b;        // Class c = b.getClass(); // 編譯錯(cuò)誤
        Class c1 = boolean.class;
        System.out.println(c1.getCanonicalName());

        Class c2 = java.io.PrintStream.class;
        System.out.println(c2.getCanonicalName());

        Class c3 = int[][][].class;
        System.out.println(c3.getCanonicalName());
    }
}//Output://boolean//java.io.PrintStream//int[][][]

(3)調(diào)用 Object 的 getClass 方法贬派,示例:

Object 類中有 getClass 方法急但,因?yàn)樗蓄惗祭^承 Object 類。從而調(diào)用 Object 類來獲取

示例:

package io.github.dunwu.javacore.reflect;import java.util.HashSet;import java.util.Set;public class ReflectClassDemo03 {    enum E {A, B}    public static void main(String[] args) {
        Class c = "foo".getClass();
        System.out.println(c.getCanonicalName());

        Class c2 = ReflectClassDemo03.E.A.getClass();
        System.out.println(c2.getCanonicalName());        byte[] bytes = new byte[1024];
        Class c3 = bytes.getClass();
        System.out.println(c3.getCanonicalName());

        Set<String> set = new HashSet<>();
        Class c4 = set.getClass();
        System.out.println(c4.getCanonicalName());
    }
}//Output://java.lang.String//io.github.dunwu.javacore.reflect.ReflectClassDemo.E//byte[]//java.util.HashSet

判斷是否為某個(gè)類的實(shí)例

判斷是否為某個(gè)類的實(shí)例有兩種方式:

  1. instanceof 關(guān)鍵字

  2. Class 對象的 isInstance 方法(它是一個(gè) Native 方法)

示例:

public class InstanceofDemo {    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();        if (arrayList instanceof List) {
            System.out.println("ArrayList is List");
        }        if (List.class.isInstance(arrayList)) {
            System.out.println("ArrayList is List");
        }
    }
}//Output://ArrayList is List//ArrayList is List

創(chuàng)建實(shí)例

通過反射來創(chuàng)建實(shí)例對象主要有兩種方式:

  • Class 對象的 newInstance 方法搞乏。

  • Constructor 對象的 newInstance 方法波桩。

示例:

public class NewInstanceDemo {    public static void main(String[] args)
        throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> c1 = StringBuilder.class;
        StringBuilder sb = (StringBuilder) c1.newInstance();
        sb.append("aaa");
        System.out.println(sb.toString());        //獲取String所對應(yīng)的Class對象
        Class<?> c2 = String.class;        //獲取String類帶一個(gè)String參數(shù)的構(gòu)造器
        Constructor constructor = c2.getConstructor(String.class);        //根據(jù)構(gòu)造器創(chuàng)建實(shí)例
        String str2 = (String) constructor.newInstance("bbb");
        System.out.println(str2);
    }
}//Output://aaa//bbb

Field

Class 對象提供以下方法獲取對象的成員(Field):

  • getFiled - 根據(jù)名稱獲取公有的(public)類成員。

  • getDeclaredField - 根據(jù)名稱獲取已聲明的類成員请敦。但不能得到其父類的類成員镐躲。

  • getFields - 獲取所有公有的(public)類成員储玫。

  • getDeclaredFields - 獲取所有已聲明的類成員。

示例如下:

public class ReflectFieldDemo {
    class FieldSpy<T> {
        public boolean[][] b = {{false, false}, {true, true}};        public String name = "Alice";        public List<Integer> list;        public T val;
    }    public static void main(String[] args) throws NoSuchFieldException {
        Field f1 = FieldSpy.class.getField("b");
        System.out.format("Type: %s%n", f1.getType());

        Field f2 = FieldSpy.class.getField("name");
        System.out.format("Type: %s%n", f2.getType());

        Field f3 = FieldSpy.class.getField("list");
        System.out.format("Type: %s%n", f3.getType());

        Field f4 = FieldSpy.class.getField("val");
        System.out.format("Type: %s%n", f4.getType());
    }
}//Output://Type: class [[Z//Type: class java.lang.String//Type: interface java.util.List//Type: class java.lang.Object

Method

Class 對象提供以下方法獲取對象的方法(Method):

  • getMethod - 返回類或接口的特定方法萤皂。其中第一個(gè)參數(shù)為方法名稱撒穷,后面的參數(shù)為方法參數(shù)對應(yīng) Class 的對象。

  • getDeclaredMethod - 返回類或接口的特定聲明方法裆熙。其中第一個(gè)參數(shù)為方法名稱端礼,后面的參數(shù)為方法參數(shù)對應(yīng) Class 的對象。

  • getMethods - 返回類或接口的所有 public 方法入录,包括其父類的 public 方法蛤奥。

  • getDeclaredMethods - 返回類或接口聲明的所有方法,包括 public纷跛、protected喻括、默認(rèn)(包)訪問和 private 方法,但不包括繼承的方法贫奠。

獲取一個(gè) Method 對象后唬血,可以用 invoke 方法來調(diào)用這個(gè)方法。

invoke 方法的原型為:

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException

示例:

public class ReflectMethodDemo {    public static void main(String[] args)
        throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {        // 返回所有方法
        Method[] methods1 = System.class.getDeclaredMethods();
        System.out.println("System getDeclaredMethods 清單(數(shù)量 = " + methods1.length + "):");        for (Method m : methods1) {
            System.out.println(m);
        }        // 返回所有 public 方法
        Method[] methods2 = System.class.getMethods();
        System.out.println("System getMethods 清單(數(shù)量 = " + methods2.length + "):");        for (Method m : methods2) {
            System.out.println(m);
        }        // 利用 Method 的 invoke 方法調(diào)用 System.currentTimeMillis()
        Method method = System.class.getMethod("currentTimeMillis");
        System.out.println(method);
        System.out.println(method.invoke(null));
    }
}

Constructor

Class 對象提供以下方法獲取對象的構(gòu)造方法(Constructor):

  • getConstructor - 返回類的特定 public 構(gòu)造方法唤崭。參數(shù)為方法參數(shù)對應(yīng) Class 的對象拷恨。

  • getDeclaredConstructor - 返回類的特定構(gòu)造方法。參數(shù)為方法參數(shù)對應(yīng) Class 的對象谢肾。

  • getConstructors - 返回類的所有 public 構(gòu)造方法腕侄。

  • getDeclaredConstructors - 返回類的所有構(gòu)造方法。

獲取一個(gè) Constructor 對象后芦疏,可以用 newInstance 方法來創(chuàng)建類實(shí)例冕杠。

示例:

public class ReflectMethodConstructorDemo {
    public static void main(String[] args)
        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<?>[] constructors1 = String.class.getDeclaredConstructors();
        System.out.println("String getDeclaredConstructors 清單(數(shù)量 = " + constructors1.length + "):");        for (Constructor c : constructors1) {
            System.out.println(c);
        }

        Constructor<?>[] constructors2 = String.class.getConstructors();
        System.out.println("String getConstructors 清單(數(shù)量 = " + constructors2.length + "):");        for (Constructor c : constructors2) {
            System.out.println(c);
        }

        System.out.println("====================");
        Constructor constructor = String.class.getConstructor(String.class);
        System.out.println(constructor);
        String str = (String) constructor.newInstance("bbb");
        System.out.println(str);
    }
}

Array

數(shù)組在 Java 里是比較特殊的一種類型,它可以賦值給一個(gè)對象引用酸茴。下面我們看一看利用反射創(chuàng)建數(shù)組的例子:

public class ReflectArrayDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> cls = Class.forName("java.lang.String");
        Object array = Array.newInstance(cls, 25);        //往數(shù)組里添加內(nèi)容
        Array.set(array, 0, "Scala");
        Array.set(array, 1, "Java");
        Array.set(array, 2, "Groovy");
        Array.set(array, 3, "Scala");
        Array.set(array, 4, "Clojure");        //獲取某一項(xiàng)的內(nèi)容
        System.out.println(Array.get(array, 3));
    }
}//Output://Scala

其中的 Array 類為 java.lang.reflect.Array 類分预。我們通過 Array.newInstance 創(chuàng)建數(shù)組對象,它的原型是:

public static Object newInstance(Class<?> componentType, int length)
    throws NegativeArraySizeException {    return newArray(componentType, length);
}

動(dòng)態(tài)代理

動(dòng)態(tài)代理是反射的一個(gè)非常重要的應(yīng)用場景薪捍。動(dòng)態(tài)代理常被用于一些 Java 框架中笼痹。例如 Spring 的 AOP ,Dubbo 的 SPI 接口酪穿,就是基于 Java 動(dòng)態(tài)代理實(shí)現(xiàn)的凳干。

靜態(tài)代理

靜態(tài)代理其實(shí)就是指設(shè)計(jì)模式中的代理模式。

代理模式為其他對象提供一種代理以控制對這個(gè)對象的訪問被济。

image

image.png

Subject 定義了 RealSubject 和 Proxy 的公共接口救赐,這樣就在任何使用 RealSubject 的地方都可以使用 Proxy 。

abstract class Subject {    public abstract void Request();
}

RealSubject 定義 Proxy 所代表的真實(shí)實(shí)體只磷。

class RealSubject extends Subject {    @Override
    public void Request() {
        System.out.println("真實(shí)的請求");
    }
}

Proxy 保存一個(gè)引用使得代理可以訪問實(shí)體净响,并提供一個(gè)與 Subject 的接口相同的接口少欺,這樣代理就可以用來替代實(shí)體。

class Proxy extends Subject {    private RealSubject real;    @Override
    public void Request() {        if (null == real) {
            real = new RealSubject();
        }
        real.Request();
    }
}

說明:

靜態(tài)代理模式固然在訪問無法訪問的資源馋贤,增強(qiáng)現(xiàn)有的接口業(yè)務(wù)功能方面有很大的優(yōu)點(diǎn)赞别,但是大量使用這種靜態(tài)代理,會(huì)使我們系統(tǒng)內(nèi)的類的規(guī)模增大配乓,并且不易維護(hù)仿滔;并且由于 Proxy 和 RealSubject 的功能本質(zhì)上是相同的,Proxy 只是起到了中介的作用犹芹,這種代理在系統(tǒng)中的存在崎页,導(dǎo)致系統(tǒng)結(jié)構(gòu)比較臃腫和松散。

動(dòng)態(tài)代理

為了解決靜態(tài)代理的問題腰埂,就有了創(chuàng)建動(dòng)態(tài)代理的想法:

在運(yùn)行狀態(tài)中飒焦,需要代理的地方,根據(jù) Subject 和 RealSubject屿笼,動(dòng)態(tài)地創(chuàng)建一個(gè) Proxy牺荠,用完之后,就會(huì)銷毀驴一,這樣就可以避免了 Proxy 角色的 class 在系統(tǒng)中冗雜的問題了休雌。

image

image.png

Java 動(dòng)態(tài)代理基于經(jīng)典代理模式,引入了一個(gè) InvocationHandler肝断,InvocationHandler 負(fù)責(zé)統(tǒng)一管理所有的方法調(diào)用杈曲。

動(dòng)態(tài)代理步驟:

  1. 獲取 RealSubject 上的所有接口列表;

  2. 確定要生成的代理類的類名胸懈,默認(rèn)為:com.sun.proxy.$ProxyXXXX担扑;

  3. 根據(jù)需要實(shí)現(xiàn)的接口信息,在代碼中動(dòng)態(tài)創(chuàng)建 該 Proxy 類的字節(jié)碼趣钱;

  4. 將對應(yīng)的字節(jié)碼轉(zhuǎn)換為對應(yīng)的 class 對象涌献;

  5. 創(chuàng)建 InvocationHandler 實(shí)例 handler,用來處理 Proxy 所有方法調(diào)用羔挡;

  6. Proxy 的 class 對象 以創(chuàng)建的 handler 對象為參數(shù),實(shí)例化一個(gè) proxy 對象间唉。

從上面可以看出绞灼,JDK 動(dòng)態(tài)代理的實(shí)現(xiàn)是基于實(shí)現(xiàn)接口的方式,使得 Proxy 和 RealSubject 具有相同的功能呈野。

但其實(shí)還有一種思路:通過繼承低矮。即:讓 Proxy 繼承 RealSubject,這樣二者同樣具有相同的功能被冒,Proxy 還可以通過重寫 RealSubject 中的方法军掂,來實(shí)現(xiàn)多態(tài)轮蜕。CGLIB 就是基于這種思路設(shè)計(jì)的。

在 Java 的動(dòng)態(tài)代理機(jī)制中蝗锥,有兩個(gè)重要的類(接口)跃洛,一個(gè)是 InvocationHandler 接口、另一個(gè)則是 Proxy 類终议,這一個(gè)類和一個(gè)接口是實(shí)現(xiàn)我們動(dòng)態(tài)代理所必須用到的汇竭。

InvocationHandler 接口

InvocationHandler 接口定義:

public interface InvocationHandler {    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

每一個(gè)動(dòng)態(tài)代理類都必須要實(shí)現(xiàn) InvocationHandler 這個(gè)接口,并且每個(gè)代理類的實(shí)例都關(guān)聯(lián)到了一個(gè) Handler穴张,當(dāng)我們通過代理對象調(diào)用一個(gè)方法的時(shí)候细燎,這個(gè)方法的調(diào)用就會(huì)被轉(zhuǎn)發(fā)為由 InvocationHandler 這個(gè)接口的 invoke 方法來進(jìn)行調(diào)用。

我們來看看 InvocationHandler 這個(gè)接口的唯一一個(gè)方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

參數(shù)說明:

  • proxy - 代理的真實(shí)對象皂甘。

  • method - 所要調(diào)用真實(shí)對象的某個(gè)方法的 Method 對象

  • args - 所要調(diào)用真實(shí)對象某個(gè)方法時(shí)接受的參數(shù)

如果不是很明白玻驻,等下通過一個(gè)實(shí)例會(huì)對這幾個(gè)參數(shù)進(jìn)行更深的講解。

Proxy 類

Proxy 這個(gè)類的作用就是用來動(dòng)態(tài)創(chuàng)建一個(gè)代理對象的類偿枕,它提供了許多的方法璧瞬,但是我們用的最多的就是 newProxyInstance 這個(gè)方法:

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

這個(gè)方法的作用就是得到一個(gè)動(dòng)態(tài)的代理對象。

參數(shù)說明:

  • loader - 一個(gè) ClassLoader 對象益老,定義了由哪個(gè) ClassLoader 對象來對生成的代理對象進(jìn)行加載彪蓬。

  • interfaces - 一個(gè) Interface 對象的數(shù)組,表示的是我將要給我需要代理的對象提供一組什么接口捺萌,如果我提供了一組接口給它档冬,那么這個(gè)代理對象就宣稱實(shí)現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了

  • h - 一個(gè) InvocationHandler 對象桃纯,表示的是當(dāng)我這個(gè)動(dòng)態(tài)代理對象在調(diào)用方法的時(shí)候酷誓,會(huì)關(guān)聯(lián)到哪一個(gè) InvocationHandler 對象上

動(dòng)態(tài)代理實(shí)例

上面的內(nèi)容介紹完這兩個(gè)接口(類)以后,我們來通過一個(gè)實(shí)例來看看我們的動(dòng)態(tài)代理模式是什么樣的:

首先我們定義了一個(gè) Subject 類型的接口态坦,為其聲明了兩個(gè)方法:

public interface Subject {    void hello(String str);    String bye();
}

接著盐数,定義了一個(gè)類來實(shí)現(xiàn)這個(gè)接口,這個(gè)類就是我們的真實(shí)對象伞梯,RealSubject 類:

public class RealSubject implements Subject {    @Override
    public void hello(String str) {
        System.out.println("Hello  " + str);
    }    @Override
    public String bye() {
        System.out.println("Goodbye");        return "Over";
    }
}

下一步玫氢,我們就要定義一個(gè)動(dòng)態(tài)代理類了,前面說個(gè)谜诫,每一個(gè)動(dòng)態(tài)代理類都必須要實(shí)現(xiàn) InvocationHandler 這個(gè)接口漾峡,因此我們這個(gè)動(dòng)態(tài)代理類也不例外:

public class InvocationHandlerDemo implements InvocationHandler {    // 這個(gè)就是我們要代理的真實(shí)對象
    private Object subject;    // 構(gòu)造方法,給我們要代理的真實(shí)對象賦初值
    public InvocationHandlerDemo(Object subject) {        this.subject = subject;
    }    @Override
    public Object invoke(Object object, Method method, Object[] args)
        throws Throwable {        // 在代理真實(shí)對象前我們可以添加一些自己的操作
        System.out.println("Before method");

        System.out.println("Call Method: " + method);        // 當(dāng)代理對象調(diào)用真實(shí)對象的方法時(shí)喻旷,其會(huì)自動(dòng)的跳轉(zhuǎn)到代理對象關(guān)聯(lián)的handler對象的invoke方法來進(jìn)行調(diào)用
        Object obj = method.invoke(subject, args);        // 在代理真實(shí)對象后我們也可以添加一些自己的操作
        System.out.println("After method");
        System.out.println();        return obj;
    }
}

最后生逸,來看看我們的 Client 類:

public class Client {
    public static void main(String[] args) {        // 我們要代理的真實(shí)對象
        Subject realSubject = new RealSubject();        // 我們要代理哪個(gè)真實(shí)對象,就將該對象傳進(jìn)去,最后是通過該真實(shí)對象來調(diào)用其方法的
        InvocationHandler handler = new InvocationHandlerDemo(realSubject);        /*
         * 通過Proxy的newProxyInstance方法來創(chuàng)建我們的代理對象槽袄,我們來看看其三個(gè)參數(shù)
         * 第一個(gè)參數(shù) handler.getClass().getClassLoader() 烙无,我們這里使用handler這個(gè)類的ClassLoader對象來加載我們的代理對象
         * 第二個(gè)參數(shù)realSubject.getClass().getInterfaces(),我們這里為代理對象提供的接口是真實(shí)對象所實(shí)行的接口遍尺,表示我要代理的是該真實(shí)對象截酷,這樣我就能調(diào)用這組接口中的方法了
         * 第三個(gè)參數(shù)handler, 我們這里將這個(gè)代理對象關(guān)聯(lián)到了上方的 InvocationHandler 這個(gè)對象上
         */
        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

        System.out.println(subject.getClass().getName());
        subject.hello("World");
        String result = subject.bye();
        System.out.println("Result is: " + result);
    }
}

我們先來看看控制臺(tái)的輸出:

com.sun.proxy.$Proxy0
Before method
Call Method: public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
Hello  World
After method

Before method
Call Method: public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()
Goodbye
After method

Result is: Over

我們首先來看看 com.sun.proxy.$Proxy0 這東西狮鸭,我們看到合搅,這個(gè)東西是由 System.out.println(subject.getClass().getName()); 這條語句打印出來的,那么為什么我們返回的這個(gè)代理對象的類名是這樣的呢歧蕉?

Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

可能我以為返回的這個(gè)代理對象會(huì)是 Subject 類型的對象灾部,或者是 InvocationHandler 的對象,結(jié)果卻不是惯退,首先我們解釋一下為什么我們這里可以將其轉(zhuǎn)化為 Subject 類型的對象赌髓?

原因就是:在 newProxyInstance 這個(gè)方法的第二個(gè)參數(shù)上,我們給這個(gè)代理對象提供了一組什么接口催跪,那么我這個(gè)代理對象就會(huì)實(shí)現(xiàn)了這組接口锁蠕,這個(gè)時(shí)候我們當(dāng)然可以將這個(gè)代理對象強(qiáng)制類型轉(zhuǎn)化為這組接口中的任意一個(gè),因?yàn)檫@里的接口是 Subject 類型懊蒸,所以就可以將其轉(zhuǎn)化為 Subject 類型了荣倾。

同時(shí)我們一定要記住,通過 Proxy.newProxyInstance 創(chuàng)建的代理對象是在 jvm 運(yùn)行時(shí)動(dòng)態(tài)生成的一個(gè)對象骑丸,它并不是我們的 InvocationHandler 類型舌仍,也不是我們定義的那組接口的類型,而是在運(yùn)行是動(dòng)態(tài)生成的一個(gè)對象通危,并且命名方式都是這樣的形式铸豁,以$開頭,proxy 為中菊碟,最后一個(gè)數(shù)字表示對象的標(biāo)號(hào)节芥。

接著我們來看看這兩句

subject.hello("World");String result = subject.bye();

這里是通過代理對象來調(diào)用實(shí)現(xiàn)的那種接口中的方法,這個(gè)時(shí)候程序就會(huì)跳轉(zhuǎn)到由這個(gè)代理對象關(guān)聯(lián)到的 handler 中的 invoke 方法去執(zhí)行逆害,而我們的這個(gè) handler 對象又接受了一個(gè) RealSubject 類型的參數(shù)头镊,表示我要代理的就是這個(gè)真實(shí)對象,所以此時(shí)就會(huì)調(diào)用 handler 中的 invoke 方法去執(zhí)行魄幕。

我們看到相艇,在真正通過代理對象來調(diào)用真實(shí)對象的方法的時(shí)候,我們可以在該方法前后添加自己的一些操作梅垄,同時(shí)我們看到我們的這個(gè) method 對象是這樣的:

public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()

正好就是我們的 Subject 接口中的兩個(gè)方法厂捞,這也就證明了當(dāng)我通過代理對象來調(diào)用方法的時(shí)候,起實(shí)際就是委托由其關(guān)聯(lián)到的 handler 對象的 invoke 方法中來調(diào)用队丝,并不是自己來真實(shí)調(diào)用靡馁,而是通過代理的方式來調(diào)用的。

小結(jié)

反射應(yīng)用

image

image

歡迎工作一到五年的Java工程師朋友們加入Java高并發(fā): 957734884机久,

群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用臭墨、高并發(fā)、高性能及分布式膘盖、Jvm性能調(diào)優(yōu)胧弛、Spring源碼,MyBatis侠畔,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)

合理利用自己每一分每一秒的時(shí)間來學(xué)習(xí)提升自己结缚,不要再用"沒有時(shí)間“來掩飾自己思想上的懶惰!趁年輕软棺,使勁拼红竭,給未來的自己一個(gè)交代!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喘落,一起剝皮案震驚了整個(gè)濱河市茵宪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瘦棋,老刑警劉巖稀火,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赌朋,居然都是意外死亡凰狞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門箕慧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來服球,“玉大人,你說我怎么就攤上這事颠焦≌缎埽” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵伐庭,是天一觀的道長粉渠。 經(jīng)常有香客問我,道長圾另,這世上最難降的妖魔是什么霸株? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮集乔,結(jié)果婚禮上去件,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好尤溜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布倔叼。 她就那樣靜靜地躺著,像睡著了一般宫莱。 火紅的嫁衣襯著肌膚如雪丈攒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天授霸,我揣著相機(jī)與錄音巡验,去河邊找鬼。 笑死碘耳,一個(gè)胖子當(dāng)著我的面吹牛显设,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辛辨,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼敷硅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了愉阎?” 一聲冷哼從身側(cè)響起绞蹦,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榜旦,沒想到半個(gè)月后幽七,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溅呢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年澡屡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咐旧。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡驶鹉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铣墨,到底是詐尸還是另有隱情室埋,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布伊约,位于F島的核電站姚淆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏屡律。R本人自食惡果不足惜腌逢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望超埋。 院中可真熱鬧搏讶,春花似錦佳鳖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吓笙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巾腕,已是汗流浹背面睛。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尊搬,地道東北人叁鉴。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像佛寿,于是被迫代替她去往敵國和親幌墓。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容