Java反射

一、簡(jiǎn)介

image.png

1、概述

Java的反射是指在程序運(yùn)行過(guò)程中, 可以構(gòu)造任意一個(gè)類的對(duì)象, 獲取任意一個(gè)類的的所有屬性和方法, 可以調(diào)用任意一個(gè)類的屬性和方法。 這種動(dòng)態(tài)獲取程序信息和動(dòng)態(tài)調(diào)用程序?qū)ο?/strong>的功能稱為Java的反射機(jī)制。反射被視為動(dòng)態(tài)語(yǔ)言關(guān)鍵。

通常情況下, 我們想調(diào)用一個(gè)Java類的屬性和方法, 必須先實(shí)例化這個(gè)Java類的對(duì)象, 然后通過(guò)對(duì)象去調(diào)用該類的屬性和方法; 通過(guò)Java的反射機(jī)制, 我們可以在程序運(yùn)行時(shí), 動(dòng)態(tài)獲取類的信息, 調(diào)用類的屬性和方法, 完成對(duì)象的實(shí)例化等操作。

如圖所示, 介紹什么是反射

image.png

  • 什么是程序運(yùn)行時(shí)刻Hello.java經(jīng)過(guò)編譯器編譯,生成字節(jié)碼文件Hello.Class, 想要運(yùn)行該程序, 就需要JVM的類加載器加載Hello.class ,然后JVM來(lái)運(yùn)行Hello.class, 程序的運(yùn)行時(shí)刻就是此時(shí)刻。
  • 什么是反射, 它的功能反射就是在程序運(yùn)行期間動(dòng)態(tài)獲取Hello。class的屬性和方法, 構(gòu)造它的實(shí)例, 這個(gè)功能就是反射。

2、反射的使用場(chǎng)景

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

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

  • 動(dòng)態(tài)代理: 在切面編程(AOP)中, 需要攔截特定的方法, 通常會(huì)選擇動(dòng)態(tài)代理方式, 此時(shí)需要反射來(lái)實(shí)現(xiàn)。

  • 注解: 注解本身只起到標(biāo)記作用, 他需要利用反射機(jī)制,根據(jù)注解標(biāo)記去調(diào)用注解解釋器, 執(zhí)行相應(yīng)行為。

  • 可擴(kuò)展功能: 應(yīng)用程序可以通過(guò)使用完全可限定類名創(chuàng)建可擴(kuò)展性對(duì)象實(shí)例來(lái)使用外部的用戶定義類剋。

3、 反射的缺點(diǎn)

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

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

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

二、原理

1、類加載過(guò)程

image.png

類加載的完整過(guò)程如下:

  1. 在編譯時(shí),Java 編譯器編譯好 .java 文件之后,在磁盤(pán)中產(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ù)類的全限定名來(lái)獲取此類的二進(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 對(duì)象。

  3. 加載結(jié)束后,JVM 開(kāi)始進(jìn)行連接階段(包含驗(yàn)證、準(zhǔn)備、初始化)。經(jīng)過(guò)這一系列操作,類的變量會(huì)被初始化。

2、 Class對(duì)象

使用反射,必須先獲得帶操作對(duì)象的Class對(duì)象。Java中, 無(wú)論生成某個(gè)類的多少個(gè)對(duì)象, 這些對(duì)象都對(duì)應(yīng)于同一個(gè)Class對(duì)象。這個(gè)Class對(duì)象有JVM生成, 通過(guò)它能夠獲悉整個(gè)類的所有結(jié)構(gòu)。所以, java.lang.Class可以視為所有反射API的入口點(diǎn)。

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

舉例來(lái)說(shuō), 加入定義了以下代碼:

User user = new User();

步驟說(shuō)明:

  • JVM加載方法的時(shí)候, 遇到new User(), JVM會(huì)根據(jù)User的全限定類名去加載User.class;
  • JVM回去從本地或網(wǎng)絡(luò)等尋找User.class文件并加載到內(nèi)存中。
  • JVM通過(guò)類加載器自動(dòng)創(chuàng)建這個(gè)類的Class對(duì)象, 并存儲(chǔ)在JVM的方法區(qū),注意一個(gè)類有且僅有一個(gè)Class對(duì)象

3、方法的反射調(diào)用

方法的反射調(diào)用, 也就是Method.invoke()方法。

    @CallerSensitive
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

Method.invoke()方法實(shí)際委派給MethodAccessor執(zhí)行。MethodAccessor接口有2個(gè)具體的實(shí)現(xiàn)類:

  • NativeMethodAccessorImpl: 本地方法來(lái)實(shí)現(xiàn)反射調(diào)用
  • DelegatingMethodAccessorImpl: 委派方法來(lái)實(shí)現(xiàn)委托調(diào)用

每個(gè)Method實(shí)例第一次調(diào)用都會(huì)生成一個(gè)委派實(shí)現(xiàn)(DelegatingMethodAccessorImpl), 他所委派的具體實(shí)現(xiàn)便是一個(gè)本地實(shí)現(xiàn)(NativeMethodAccessorImpl)。本地實(shí)現(xiàn)非常容易理解, 當(dāng)進(jìn)入Java虛擬機(jī)內(nèi)部后, 我們便擁有了Method實(shí)例所指向方法的具體地址, 這個(gè)時(shí)候反射無(wú)非是將準(zhǔn)備好的參數(shù), 然后調(diào)用目標(biāo)方法。

  • 生成MethodAccessor
    private MethodAccessor acquireMethodAccessor() {
        // First check to see if one has been created yet, and take it
        // if so
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            // Otherwise fabricate one and propagate it up to the root
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }

        return tmp;
    }
    public MethodAccessor newMethodAccessor(Method var1) {
        checkInitted();
        if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
            return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
        } else {
            NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
           // 生成DelegatingMethodAccessorImpl, 實(shí)際委派的是NativeMethodAccessorImpl 
            DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
            var2.setParent(var3);
            return var3;
        }
    }
  • DelegatingMethodAccessorImpl源碼:
// 一個(gè)典型的靜態(tài)代理模式
class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
        this.setDelegate(var1);
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        return this.delegate.invoke(var1, var2);
    }

    void setDelegate(MethodAccessorImpl var1) {
        this.delegate = var1;
    }
}

Method實(shí)例的每次調(diào)用都是先調(diào)用DelegatingMethodAccessorImplinvoke方法, 在調(diào)用NativeMethodAccessorImplinvoke方法。

其實(shí),Java 的反射調(diào)用機(jī)制還設(shè)立了另一種動(dòng)態(tài)生成字節(jié)碼的實(shí)現(xiàn)(下稱動(dòng)態(tài)實(shí)現(xiàn)),直接使用 invoke 指令來(lái)調(diào)用目標(biāo)方法。之所以采用委派實(shí)現(xiàn),便是為了能夠在本地實(shí)現(xiàn)以及動(dòng)態(tài)實(shí)現(xiàn)中切換。動(dòng)態(tài)實(shí)現(xiàn)和本地實(shí)現(xiàn)相比,其運(yùn)行效率要快上 20 倍。這是因?yàn)閯?dòng)態(tài)實(shí)現(xiàn)無(wú)需經(jīng)過(guò) Java 到 C++ 再到 Java 的切換,但由于生成字節(jié)碼十分耗時(shí),僅調(diào)用一次的話,反而是本地實(shí)現(xiàn)要快上 3 到 4 倍。

考慮到許多反射調(diào)用僅會(huì)執(zhí)行一次,Java 虛擬機(jī)設(shè)置了一個(gè)閾值 15(可以通過(guò) -Dsun.reflect.inflationThreshold 來(lái)調(diào)整),當(dāng)某個(gè)反射調(diào)用的調(diào)用次數(shù)在 15 之下時(shí),采用本地實(shí)現(xiàn);當(dāng)達(dá)到 15 時(shí),便開(kāi)始動(dòng)態(tài)生成字節(jié)碼,并將委派實(shí)現(xiàn)的委派對(duì)象切換至動(dòng)態(tài)實(shí)現(xiàn),這個(gè)過(guò)程我們稱之為 Inflation。

源碼如下:

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method var1) {
        this.method = var1;
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }

        return invoke0(this.method, var1, var2);
    }

    void setParent(DelegatingMethodAccessorImpl var1) {
        this.parent = var1;
    }

    private static native Object invoke0(Method var0, Object var1, Object[] var2);
}
public class ReflectionFactory {
    private static boolean initted = false;
    private static final Permission reflectionFactoryAccessPerm = new RuntimePermission("reflectionFactoryAccess");
    private static final ReflectionFactory soleInstance = new ReflectionFactory();
    private static volatile LangReflectAccess langReflectAccess;
    private static volatile Method hasStaticInitializerMethod;
    private static boolean noInflation = false;
    private static int inflationThreshold = 15;

  static int inflationThreshold() {
        return inflationThreshold;
    }

}

4、反射調(diào)用的開(kāi)銷

方法的反射會(huì)帶來(lái)不小的開(kāi)銷, 原有如下:

  • 變長(zhǎng)參數(shù)方法導(dǎo)致的Object數(shù)組
  • 基本類型的自動(dòng)裝箱拆箱
  • 最重要的方法內(nèi)聯(lián)

Class.forName會(huì)調(diào)用本地方法,Class.getMethod會(huì)遍歷該類的所有公共方法, 如果沒(méi)有匹配到, 還會(huì)遍歷父類的所有公共方法, 可想而知這兩個(gè)操作都非常費(fèi)時(shí)。

注意,以getMethod為代表的查找方法操作, 會(huì)返回查找結(jié)果的一份拷貝。因此我們應(yīng)當(dāng)在熱點(diǎn)代碼中避免使用返回Method數(shù)組的getMethodsgetDeclareMethods方法,以減少不必要的堆空間消耗。在實(shí)踐中, 往往會(huì)在應(yīng)用中緩存Class.forNameClass.getMethod的結(jié)果。

反射調(diào)用自身的性能開(kāi)銷:

第一,由于Method.invoke方法自身是一個(gè)可變長(zhǎng)參數(shù)方法,在字節(jié)碼層面它的最后一個(gè)參數(shù)是一個(gè)Object數(shù)組。Java編譯器會(huì)在調(diào)用處生成一個(gè)長(zhǎng)度為傳入?yún)?shù)數(shù)量的Object數(shù)組。

第二,Object不能存儲(chǔ)基本數(shù)據(jù)類型, Java編譯器會(huì)對(duì)傳入的基本數(shù)據(jù)類型進(jìn)行自動(dòng)裝箱、拆箱。

這兩個(gè)操作都很耗時(shí), 還可能占用堆內(nèi)存,是的GC頻繁。

三、常用API

Java類的成員包括以下三類: 屬性字段、構(gòu)造函數(shù)、方法。 反射的API也是和這幾個(gè)相關(guān)。


image.png
  • Field類:提供有關(guān)類的屬性信息嗎,以及它的動(dòng)態(tài)訪問(wèn)權(quán)限。它是一個(gè)封裝反射類的屬性的類。
  • Constructor類: 提供類的構(gòu)造方法信息, 以及對(duì)它的動(dòng)態(tài)訪問(wèn)權(quán)限。它是一個(gè)封裝反射類的構(gòu)造器的類。
  • Method類: 提供類的方法信息,包括抽象方法。它是一個(gè)封裝反射類的方法的類。
  • Class類: 表示Java應(yīng)用程序中的類的實(shí)例。

通過(guò)一個(gè)經(jīng)典的例子, 我們來(lái)學(xué)習(xí)反射。 新建一個(gè)Student類:

public class Student {

    private static volatile Student INSTANCE;
    private String name;
    public int age;

    private Student(){}

    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private String show(String message){
        System.out.println("show: " + name + "," + age+ "," + message);
        return "testReturnValue";
    }

    public static Student getInstance(){
        if(INSTANCE==null){
            synchronized (Student.class){
                if(INSTANCE==null){
                    INSTANCE = new Student();
                }
            }
        }
        return INSTANCE;
    }
}

該類包含2個(gè)成員變量、1個(gè)類變量、2個(gè)構(gòu)造器、6個(gè)方法,基本覆蓋平常所用的所有類成員。

1、java.lang.reflect

Java中java.lang.reflect包提供了反射功能, 它下面的類都沒(méi)有public構(gòu)造方法。 它包含的核心接口和類如下(不包含已經(jīng)介紹過(guò)的):

  • Member接口:反映關(guān)于單個(gè)成員(字段或方法)或構(gòu)造函數(shù)的標(biāo)識(shí)信息。
  • Array類:該類提供動(dòng)態(tài)地生成和訪問(wèn) JAVA 數(shù)組的方法。
  • Modifier類:提供了 static 方法和常量,對(duì)類和成員訪問(wèn)修飾符進(jìn)行解碼。
  • Proxy類:* 提供動(dòng)態(tài)地生成代理類和類實(shí)例的靜態(tài)方法。

2、獲取Class對(duì)象

獲取Class對(duì)象的3種方法:

  1. Class.forName方法:
public class UserClient {
    public static void main(String[] args) throws ClassNotFoundException {
        // 加載一個(gè)類
        Class<?> studentClass = Class.forName("org.example.good.reflect.Student");
        System.out.println(studentClass.getCanonicalName());

        // 加載基礎(chǔ)類型
        Class<?> arrayClass = Class.forName("[D");
        System.out.println(arrayClass.getCanonicalName());

        // 加載一個(gè)類的一維數(shù)組, [代表一維數(shù)組, L和;之間的代表全限定類名
        Class<?> arrayClass2 = Class.forName("[Lorg.example.good.reflect.Student;");
        System.out.println(arrayClass2.getCanonicalName());

        // 加載一個(gè)類的二維數(shù)組
        Class<?> arrayClass3 = Class.forName("[[Lorg.example.good.reflect.Student;");
        System.out.println(arrayClass3);


    }
}

使用類的全限定類名來(lái)反射對(duì)象的類, 常用的場(chǎng)景是JDBC中加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)。

  1. 類名 + .class方法:
    直接使用類名+'.class'獲取Class對(duì)象。
public class UserClient {
    public static void main(String[] args) throws ClassNotFoundException {
        // 加載一個(gè)類
        Class<?> studentClass = Class.forName("org.example.good.reflect.Student");
        System.out.println(studentClass.getCanonicalName());

        Class<?> studentClass2 = Student.class;
        System.out.println(studentClass2.getCanonicalName());
        System.out.println(studentClass2 == studentClass);


    }
}
  1. ObjectgetClass方法:
    Object類中有getClass方法,因?yàn)樗蓄惗祭^承自Object類。因此可以調(diào)用Object.getClass方法來(lái)獲取Class對(duì)象。
public class UserClient {
    public static void main(String[] args) throws ClassNotFoundException {
        // 加載一個(gè)類
        Class<?> studentClass = Class.forName("org.example.good.reflect.Student");
        System.out.println(studentClass.getCanonicalName());

        Student student = new Student("王子", 1);
        Class<?> studentClass3 = student.getClass();
        System.out.println(studentClass3.getCanonicalName());
        System.out.println(studentClass == studentClass3);

    }
}

3、是否某個(gè)類的實(shí)例

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

  • instanceof的關(guān)鍵字
  • Class.isInstance方法(它是一個(gè)native方法)
public class UserClient {
    public static void main(String[] args) throws ClassNotFoundException {
        
        List<String> dataList = new ArrayList<>();
        System.out.println(dataList instanceof List);
        System.out.println(List.class.isInstance(dataList));
    }
}

4、創(chuàng)建類的實(shí)例

創(chuàng)建類的實(shí)例有2種方法:

  • 使用Class對(duì)象的newInstance方法;
  • 使用Constructor對(duì)象的newInsatnce方法:
        Class<?> clazz1 = Student.class;
        Student student1 = (Student) clazz1.newInstance();
        System.out.println(student1);

        Constructor<?> constructor = clazz1.getConstructor(String.class, Integer.class);
        Student student2 = (Student)constructor.newInstance("王子", 1);
        System.out.println(student2);

注意事項(xiàng):

  • 使用Class對(duì)象的newInstance方法時(shí),要求對(duì)應(yīng)的類必須定義一個(gè)public的無(wú)參構(gòu)造方法, 如果無(wú)參構(gòu)造方法設(shè)置成privateprotected但沒(méi)有訪問(wèn)權(quán)限, 就會(huì)報(bào)一個(gè)IllegalAccessException錯(cuò)誤。
  • 使用Constructor對(duì)象的newInstance方法時(shí), 構(gòu)造方法的入?yún)⒈仨毑荒苁腔A(chǔ)類型, 否則會(huì)報(bào)參數(shù)不匹配。

5、創(chuàng)建數(shù)組實(shí)例

數(shù)組在Java中是一個(gè)特殊的數(shù)據(jù)類型,它可以復(fù)制給一個(gè)對(duì)象引用。Java中通過(guò)Array.newInstance創(chuàng)建數(shù)組的實(shí)例。利用反射創(chuàng)建數(shù)組:

  Class<?> clazz = Integer.class;
        Object object = Array.newInstance(clazz, 5);
        Array.set(object, 0, 1);
        System.out.println(object);
        System.out.println(Array.getLength(object));
        System.out.println(Array.get(object, 4));
        System.out.println(Array.get(object, 0));

java.lang.reflect.Array的源碼:

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

6、Field

Class對(duì)象提供以下方法獲取Field。

  • getField(String name): 根據(jù)名稱獲取公共類成員, 包括父類。
  • getFields(): 獲取所有公共類成員, 包括父類。
  • getDeclareField(String name): 獲取所有類成員, 不包括父類。
  • getDeclareFields(): 獲取所有類成員, 不包括父類。
    System.out.println("list public field:");
        Class<?> clazz = Student.class;
        Field[] fields = clazz.getFields();
        for(Field field: fields){
            System.out.println(String.format("field name: %s, type: %s", field.getName(), field.getType().getName()));
        }

        System.out.println("list all field:");
        Field[] fields2 = clazz.getDeclaredFields();
        for(Field field: fields2){
            System.out.println(String.format("field name: %s, type: %s", field.getName(), field.getType().getName()));
        }

        Field field = clazz.getField("age");
        Field field2 = clazz.getDeclaredField("name");
        System.out.println(String.format("field name: %s, type: %s", field.getName(), field.getType()));

        Student student =new Student("", 0);
        System.out.println(String.format("before, field value: %s", field.get(student)));
        field.set(student, 25);
        System.out.println(String.format("after, field value: %s", field.get(student)));

        field2.setAccessible(true);
        field2.set(student, "小可愛(ài)");
        System.out.println(String.format("after, field value: %s", field2.get(student)));

// Output
list public field:
field name: age, type: java.lang.Integer
list all field:
field name: INSTANCE, type: org.example.good.reflect.Student
field name: name, type: java.lang.String
field name: age, type: java.lang.Integer
field name: age, type: class java.lang.Integer
before, field value: 0
after, field value: 25
after, field value: 小可愛(ài)

7、Method

Class對(duì)象提供以下方法獲取Method

  • getMethod(String name, Class<?>... parameterTypes): 根據(jù)方法名稱獲取指定公共方法, 包含父類公共方法。其中第一個(gè)參數(shù)為方法名稱, 后面參數(shù)時(shí)方法入?yún)⒌腃lass對(duì)象。

  • getMethods(): 獲取所有公共方法, 包含父類公共方法。

  • getDeclareMethod(String name, Class<?>... parameterTypes): 根據(jù)方法名稱獲取指定方法, 包含所有公共非公共方法, 但不包含父類方法。其中第一個(gè)參數(shù)為方法名稱, 后面參數(shù)時(shí)方法入?yún)⒌腃lass對(duì)象。

  • getDeclareMethod(): 獲取所有該類方法, 不包含父類方法。

        Class<?> clazz = Student.class;
        Method method = clazz.getMethod("getAge");
        System.out.println(method.getName());

        Method[] methods = clazz.getDeclaredMethods();
        for(Method method1: methods){
            System.out.println(String.format("%s", method1.getName()));
        }

// Output
// getAge
// getName
// getName
// getInstance
// setName
// setAge
// show
// getAge

獲取一個(gè)Method對(duì)象后, 可以調(diào)用invoke方法類調(diào)用改方法。

        Student student = (Student)clazz.newInstance();
        Field field = clazz.getDeclaredField("name");
        Method method2 = clazz.getMethod("setAge", int.class);
        method2.invoke(student, 25);
        field.setAccessible(true);
        field.set(student, "Lily");

        Method method1 = clazz.getDeclaredMethod("show", String.class);
        method1.setAccessible(true);
        method1.invoke(student, "hello");
        
        // Output
       show: Lily,25,hello

Method 調(diào)用invoke() 的時(shí)候,存在許多細(xì)節(jié):

invoke() 方法中第一個(gè)參數(shù) Object 實(shí)質(zhì)上是 Method 所依附的 Class 對(duì)應(yīng)的類的實(shí)例,如果這個(gè)方法是一個(gè)靜態(tài)方法,那么 ojb 為 null,后面的可變參數(shù) Object 對(duì)應(yīng)的自然就是參數(shù)。

invoke() 返回的對(duì)象是 Object,所以實(shí)際上執(zhí)行的時(shí)候要進(jìn)行強(qiáng)制轉(zhuǎn)換。

在對(duì)Method調(diào)用invoke()的時(shí)候,如果方法本身會(huì)拋出異常,那么這個(gè)異常就會(huì)經(jīng)過(guò)包裝,由Method統(tǒng)一拋InvocationTargetException。而通過(guò)InvocationTargetException.getCause() 可以獲取真正的異常。

8、Constructor

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

  • getConstructor: 返回類的特定 public 構(gòu)造方法。參數(shù)為方法參數(shù)對(duì)應(yīng) Class 的對(duì)象。
  • getDeclaredConstructor: 返回類的特定構(gòu)造方法。參數(shù)為方法參數(shù)對(duì)應(yīng) Class 的對(duì)象。
  • getConstructors: 返回類的所有 public 構(gòu)造方法。
  • getDeclaredConstructors: 返回類的所有構(gòu)造方法。

獲取一個(gè)Constructor 對(duì)象后,可以用newInstance 方法來(lái)創(chuàng)建類實(shí)例。

        Constructor<?> constructor = clazz.getConstructor();
        Student student = (Student) constructor.newInstance();

        Constructor<?> constructor1 = clazz.getConstructor(String.class, Integer.class);
        Student student1 = (Student) constructor1.newInstance("lily", 25);

        System.out.println(student.toString());
        System.out.println(student1.toString());

        // Output
        Student{name='null', age=null}
        Student{name='lily', age=25}

9、繞開(kāi)訪問(wèn)限制

有時(shí)候,我們需要通過(guò)反射訪問(wèn)私有成員、方法??梢允褂?Constructor/Field/Method.setAccessible(true) 來(lái)繞開(kāi) Java 語(yǔ)言的訪問(wèn)限制。

四、總結(jié)

參考資料
https://dunwu.github.io/javacore/basics/java-reflection.html#_4-3-cglib-%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、概述 1、Java反射機(jī)制(Java-Reflect): 在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類中的所...
    年少懵懂丶流年夢(mèng)閱讀 4,585評(píng)論 0 5
  • 問(wèn)題: 在運(yùn)行時(shí),對(duì)一個(gè)JAVA類,能否知道屬性和方法;能否調(diào)用它的任意方法? 答案是可以的,JAVA提供一種反射...
    糖寶_閱讀 804評(píng)論 0 1
  • 生活中迷茫感的產(chǎn)生,往往源之于堅(jiān)持一件事放棄的那一刻,因?yàn)閺哪且豢唐?,你開(kāi)始變得無(wú)聊,變得沒(méi)有方向感。 運(yùn)行環(huán)境 ...
    PaperCy閱讀 673評(píng)論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂(lè)有人憂愁,有人驚喜有人失落,有的覺(jué)得收獲滿滿有...
    陌忘宇閱讀 8,861評(píng)論 28 54
  • 人工智能是什么?什么是人工智能?人工智能是未來(lái)發(fā)展的必然趨勢(shì)嗎?以后人工智能技術(shù)真的能達(dá)到電影里機(jī)器人的智能水平嗎...
    ZLLZ閱讀 4,105評(píng)論 0 5

友情鏈接更多精彩內(nèi)容