Java核心知識體系5:反射機制詳解


Java核心知識體系1:泛型機制詳解
Java核心知識體系2:注解機制詳解
Java核心知識體系3:異常機制詳解
Java核心知識體系4:AOP原理和切面應用

1 介紹

無論是那種語言體系,反射都是必不可少的一個技術特征。從Java體系來說,很多常用的技術框架或多或少都使用到了反射技術,比如Spring、MyBatis、RocketMQ、FastJson 等等。反射技術強大而必要,在大多數框架中起到舉足輕重的作用。所以,反射也是Java必不可少的核心技術之一。

接下來我們來看看反射的一些技術要點:

  1. 反射的概念(即什么是反射)?
  2. 反射的作用(它幫我們解決了哪些問題)?
  3. 反射的實現原理?
  4. 如何使用反射?
    下面我就針對以上的疑問,一一來講解。

1.1 反射是什么?

Java反射(Reflection)是Java語言的一個核心特性,它允許運行中的Java代碼對自身進行自我檢查,甚至修改自身的組件。具體來說,反射機制提供了在運行狀態中,對於任意一個類,都能夠了解這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性。這種動態獲取的信息以及動態調用對象的方法在Java中就叫做反射。
一句話總結:反射就是在運行時才具體知曉要操作的類是什么結構,並在運行時獲取類的完整構造,並調用對應的方法、屬性等。

Java的反射主要包括以下三個部分:

  • 類的加載:Java的類在需要使用時才會被加載到JVM中。這個過程是由類加載器(ClassLoader)完成的。類加載器首先檢查這個類是否已經被加載過,如果還沒有加載,那么就會從磁盤上加載類的字節碼並創建一個Class對象。
  • 獲取類的信息:當一個對象被創建后,我們可以使用反射來獲取這個對象的Class對象。通過這個Class對象,我們可以獲取到這個類的所有屬性和方法。
  • 方法的調用:通過反射,我們可以動態的調用一個對象的方法。即使這個方法是一個私有的方法,也能夠通過反射來調用。

1.2 為什么要用反射?

Java Reflection功能非常強大,並且非常有用,比如:

  • 獲取任意類的名稱、package信息、所有屬性、方法、注解、類型、類加載器等
  • 獲取任意對象的屬性,並且能改變對象的屬性
  • 調用任意對象的方法
  • 判斷任意一個對象所屬的類
  • 實例化任意一個類的對象
  • 通過反射我們可以實現動態裝配,降低代碼的耦合度,實現動態代理等。

具體的應用場景:

  • 框架設計:許多框架,如Spring,Hibernate等,都大量使用了反射來實現對象的自動裝配,動態代理等功能。
  • 單元測試:單元測試框架(如JUnit)會使用反射來調用被注解的方法。
  • 插件化:為了實現插件化,可以通過反射加載不同的插件。
  • 對象序列化與反序列化:在對象進行序列化和反序列化的時候,會使用反射獲取到對象的所有屬性和方法。

2 反射的使用

在Java中,Class類與java.lang.reflect類庫配合對反射技術進行了完整的支持。在反射的Package中,我們經常使用功能類如下:

  • Constructor類表示的是Class 對象所表示的類的構造方法,利用它可以在運行時動態創建對象
  • Field類表示Class對象所表示的類的成員變量,通過它可以在運行時動態修改成員變量的屬性值(包含private)
  • Method類表示Class對象所表示的類的成員方法,通過它可以動態調用對象的方法(包含private)

下面將對這幾個類進行詳細介紹。

2.1 反射創建類對象

一般情況下我們通過反射創建類對象主要有兩種方式:

  • 通過 Class 對象的 newInstance() 方法

  • 通過 Constructor 對象的 newInstance() 方法

  • 通過 Class 對象的 newInstance() 方法實現

Class clz = Class.forName("com.ad.reflection.TestRefle");
TestRefle tr= (TestRefle)clz.newInstance();
  • 通過 Constructor 對象的 newInstance() 方法實現
Class clz = Class.forName("com.ad.reflection.TestRefle");
Constructor constructor = clz.getConstructor();
TestRefle tr= (TestRefle)constructor.newInstance();

這邊需要注意,通過 Constructor 對象創建類對象可以選擇特定構造方法,而通過 Class 對象則只能使用默認的無參數構造方法。
下面的代碼演示的是通過 Constructor 調用有參構造方法進行了類對象初始化:

Class clz = Class.forName("com.ad.reflection.TestRefle");
Constructor constructor = clz.getConstructor(String.class);
TestRefle tr= (TestRefle)constructor.newInstance("提供一個String參數");

接下來我們繼續,通過具體的API獲取詳細的類信息:類信息、方法信息、屬性信息等。

2.2 獲取Class類對象

 // 獲取Class對象的三種方式
 根據類名: Class mailClass = MailInfo.class;
 根據對象: Class mailClass = new MailInfo().getClass();
 根據全限定類名: Class mailClass = Class.forName("com.ad.MailInfo");
 
 // 根據對象獲取信息和實例對象
 獲取全限定類名: mailClass.getName();
 獲取類名: mailClass.getSimpleName();
 實例化: userClass.getDeclaredConstructor().newInstance();

更加詳細Class類獲取參考如下:

方法 用途
forName() (1)獲取Class對象的一個引用,但引用的類還沒有加載(該類的第一個對象沒有生成)就加載了這個類。 (2)為了產生Class引用,forName()立即就進行了初始化。
Object-getClass() 獲取Class對象的一個引用,返回表示該對象的實際類型的Class引用。
getName() 取全限定的類名(包括包名),即類的完整名字。 getSimpleName() 獲取類名(不包括包名)
getCanonicalName() 獲取全限定的類名(包括包名)
isInterface() 判斷Class對象是否是表示一個接口
getInterfaces() 返回Class對象數組,表示Class對象所引用的類所實現的所有接口。
getSupercalss() 返回Class對象,表示Class對象所引用的類所繼承的直接基類。應用該方法可在運行時發現一個對象完整的繼承結構。
newInstance() 返回一個Oject對象,是實現“虛擬構造器”的一種途徑。使用該方法創建的類,必須帶有無參的構造器。

2.3 獲取類的成員變量的信息

Field[] fields = _class.getDeclaredFields();

更加詳細成員變量獲取參考如下:

方法 用途
getField(String name) 獲得某個公有的屬性對象
getFields() 獲取所有的公有的屬性對象
getDeclaredField(String name) 獲得某個屬性對象(public和非public)
getDeclaredFields() 獲得所有屬性對象(public和非public)

2.4 獲得類方法

Method[] methods = _class.getDeclaredMethods();

更加詳細方法獲取參考如下:

方法 用途
getMethod(String name, Class...<?> paramerterTypes) 獲得某個公有的方法對象
getMethods() 獲取所有的公有的方法對象
getDeclaredMethod(String name, Class...<?> paramerterTypes) 獲得對應類下某個方法(public和非public)
getDeclaredMethods() 獲得對應類下所有方法(public和非public)

2.5 獲得構造函數

Constructor[] constructors = _class.getDeclaredConstructors();

更加詳細構造函數獲取參考如下:

方法 用途
getConstructor(Class...<?> paramerterTypes) 獲得該類中與參數類型匹配的公有構造方法
getConstructors() 獲取該類的所有公有構造方法
getDeclaredConstructor(Class...<?> paramerterTypes) 獲得該類中與參數類型匹配的構造方法
getDeclaredConstructors() 獲取該類的所有構造方法

這樣通過反射就可以做在運行時獲取類的完整構造,並獲得類信息了。

類名 用途
Class類 代表類的實體,在運行的Java應用程序中表示類和接口
Field類 代表類的成員變量(即類的屬性)
Method類 代表類的方法
Constructor類 代表類的構造函數

通過上面的幾個示例我們基本了解了反射的使用,但這僅僅是使用,我們還需深入理解反射背后的底層實現原理。

3 反射原理分析

3.1 反射的調用流程

1、編寫完Java項目之后,java文件都會被編譯成一個.class文件
2、這些class文件在程序運行時會被ClassLoader加載到JVM中,當一個類被加載以后,JVM就會在內存中自動產生一個Class對象。
3、通過Class對象獲取 Field(屬性)、Method(方法)、Construcor(構造函數)
我們平時通過new的形式創建對象,本質上就是通過Class來創建個新對象
image
通過上面的流程我們可以看出反射的優勢:

  • 動態裝配

我們的程序在運行時,可能不一定會用到所有我們編寫和構建的類,這樣避免啟動時間太長並且浪費大量無用的機器資源。
取而代之的是動態的加載一些類,這些類可能之前用不到所以不用加載到jvm,而是在運行時根據需要才加載。

  • 降低耦合
    如果你在使用new時明確的指定類名,那這就是典型的硬編碼實現,而在使用反射的時候,可以只傳入類名參數,就可以生成對象,降低了耦合度,使得程序更具靈性。

完整的調用流程,圖片來自網上,比較模糊,后續再補一個
image

3.2 反射的應用場景

image

  • 框架設計:許多框架,如Spring,Hibernate,mybatis,dubbo,rocketmq等,都大量使用了反射來實現對象的自動裝配,動態代理等功能。
  • 單元測試:單元測試框架(如JUnit)會使用反射來調用被注解的方法。
  • 插件化:為了實現插件化,可以通過反射加載不同的插件。
  • 對象序列化與反序列化:在對象進行序列化和反序列化的時候,會使用反射獲取到對象的所有屬性和方法。
  • 動態配置、動態代理:通過反射去讀取配置,以及代理請求

4 反射經典案例解析

以下案例來自百度文心一言大模型自動生成,已調試通過。

import java.lang.reflect.Method;  
  
public class ReflectionExample {  
    public static void main(String[] args) {  
        try {  
            // 獲取目標類的Class對象  
            Class<?> targetClass = Class.forName("java.util.ArrayList");  
  
            // 獲取目標類的所有公共方法  
            Method[] methods = targetClass.getMethods();  
  
            // 遍歷所有方法並打印方法名  
            for (Method method : methods) {  
                System.out.println(method.getName());  
            }  
  
            // 獲取特定方法,比如添加元素的add方法  
            Method addMethod = targetClass.getMethod("add", Object.class);  
  
            // 創建目標類的實例對象  
            Object targetObject = targetClass.newInstance();  
  
            // 調用add方法添加元素  
            addMethod.invoke(targetObject, "Hello, World!");  
  
            // 獲取目標類的所有屬性(字段)並打印屬性名  
            Field[] fields = targetClass.getDeclaredFields();  
            for (Field field : fields) {  
                System.out.println(field.getName());  
            }  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

這個案例展示了如何使用反射來獲取目標類的Class對象,獲取並打印目標類的所有公共方法,獲取特定方法,創建目標類的實例對象,調用目標類的方法,以及獲取並打印目標類的所有屬性(字段)。

總結

無論是那種語言體系(C#、Java等等),反射都是必不可少的一個技術特征。而從Java體系來說,很多常用的技術框架或多或少都使用到了反射技術,比如Spring、MyBatis、RocketMQ、FastJson 等等。
學習好Java 反射技術能幫助你更好的理解底層調用的原理,也有助於設計更加 輕巧、高內聚、低耦合 的業務框架。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2024 CODEPRJ.COM