Java 反射 Bean 屬性 API
1.概述
Java 反射提供了強大的PropertyDescriptor
,可以在執行時間檢查和操作物件。 Beans Property API ( java.beans.PropertyDescriptor
) 和 Apache Commons BeanUtils 是兩種常用的動態處理物件屬性的工具。 PropertyDescriptor 支援透過 getter 和 setter 方法對屬性進行低階訪問,而 Commons BeanUtils
則簡化了諸如讀取、寫入、從映射填充以及複製 Bean 屬性等常見操作。
在本教程中,我們將詳細討論 Java Reflections Beans Property API。
2. 真實範例:使用 Post Bean
讓我們先建立一個實用且簡單的 Java bean 來表示部落格文章實體。現實世界中的軟體通常需要動態檢查或操作物件屬性,尤其是在嚴重依賴元資料和配置的框架和庫中,例如物件關係映射 (ORM) 工具、序列化庫和 UI 框架。以下Post
類別示範了一個典型的 Java bean,其中包含標題、作者和元資料屬性:
public class Post {
private String title;
private String author;
private Metadata metadata;
// Getters and Setters
public static class Metadata {
private int wordCount;
// Getters and Setters
}
}
接下來,我們將詳細研究如何在執行時間動態檢查和操作這些屬性,並展示實際開發場景中的常見用例。
3.使用PropertyDescriptor
和BeanInfo
在深入研究具體方法之前,先了解我們將要使用的兩個主要 Java 自省類別—— BeanInfo
和PropertyDescriptor
是大有裨益的。 BeanInfo BeanInfo
封裝了 Java Bean 的相關信息,詳細說明了其屬性、方法和事件。另一方面, PropertyDescriptor
表示單一屬性的元數據,提供對 getter 和 setter 方法的動態存取。這些類別構成了 Java 自省功能的支柱,使我們能夠輕鬆地執行複雜的運行時屬性操作。
3.1. 使用Introspector.getBeanInfo()
檢查 Bean 屬性
我們可以透過Introspector
類別檢查 bean 的結構:
BeanInfo beanInfo = Introspector.getBeanInfo(Post.class);
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
System.out.println(pd.getName());
}
在此範例中,Java 自省識別了 Post bean 內的所有屬性,包括來自 Java 的 Object 超類別的隱式「class」屬性:
author
class
metadata
title
使用此類,我們可以在運行時檢查 Bean 的屬性。這是一種讓應用程式更加靈活、適應性更強的實用方法。
3.2. 定位屬性元資料
當我們想要在執行時間操作單一屬性時,第一步是為目標屬性和 bean 類別建立一個PropertyDescriptor
:
PropertyDescriptor titlePd = new PropertyDescriptor("title", Post.class);
我們可以把這個描述符想像成一張小“備忘單”,它告訴 Java 它需要知道的有關屬性的所有信息 - 甚至包括它的 getter 和 setter 方法所在的位置。
3.3. 提取訪問器方法
一旦我們有了描述符,我們就可以檢索屬性的存取器方法:
Method write = titlePd.getWriteMethod();
Method read = titlePd.getReadMethod();
設定器(寫入)用於更新屬性值,而獲取器(讀取)用於檢索屬性值。這一步連接了元資料和運行時執行。
由於如果 getter 或 setter 不存在則可能會傳回null
,因此我們應該始終在呼叫之前進行檢查。
3.4. 反射調用方法
最後,我們使用Method.invoke()
來呼叫特定 bean 實例上的存取器:
if (write != null) {
write.invoke(post, "Reflections in Java");
}
if (read != null) {
String value = (String) read.invoke(post);
System.out.println(value);
}
由於Method.invoke()
總是傳回一個Object
,因此在讀取時我們必須將結果轉換為正確的型別。
透過遵循以下三步驟方法:描述符建立、存取器提取、呼叫——我們可以動態讀取或寫入任何 Java-Bean 屬性,同時仍執行隱藏在其 getter 和 setter 中的 bean 的業務邏輯(驗證、事件、日誌)。
4. 使用 Apache Commons BeanUtils
更快運作
Apache Commons BeanUtils
基於相同的反射機制構建,但隱藏了大部分樣板程式碼。在深入研究各個操作之前,我們先聲明一下用於拉取該程式庫的Maven 相依性:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.11.0</version>
</dependency>
4.1. 簡單屬性訪問
BeanUtils
提供的第一個改進是BeanUtils.setProperty()
和BeanUtils.getProperty()
。這些輔助方法使我們無需手動建立PropertyDescriptor
:
Post post = new Post();
BeanUtils.setProperty(post, "title", "Commons BeanUtils Rocks");
String title = BeanUtils.getProperty(post, "title");
在底層,函式庫仍然透過反射發現適當的 getter 和 setter,但調用網站保持簡短且富有表現力。
4.2. 從 Map 填滿 Bean
Web 程式碼通常以鍵值對的形式接收資料(例如,請求參數或反序列化為 Map<String, Object>
BeanUtils
物件)。 BeanUtils 可以在一行程式碼中將整個 Bean 合併:
Map<String, Object> data = Map.of("title", "Map → Bean", "author", "Baeldung Team");
BeanUtils.populate(post, data);
在內部, populate()
遍歷映射並委託給每個條目的setProperty()
,從而為我們在動態類型輸入和強類型域模型之間建立簡潔的橋樑。
4.3. 複製 Bean 屬性
在兩個 Java 物件之間傳輸狀態是另一個常見任務。 BeanUtils.copyProperties BeanUtils.copyProperties()
執行淺拷貝,其中複製匹配的名稱,並忽略不匹配的名稱:
Post source = new Post();
source.setTitle("Source");
source.setAuthor("Alice");
Post target = new Post();
BeanUtils.copyProperties(target, source);
由於此方法是反射型的,因此我們可以在兩個 bean 不共用公共介面的情況下執行上述語句。這種模式在 DTO 和 JPA 實體之間映射時非常有用。
4.4. 類型轉換與巢狀路徑
除了普通字串之外, BeanUtils
還利用ConvertUtils
自動強制轉換原始型別。我們可以為特定領域的類別註冊額外的轉換器:
ConvertUtils.register(new LocalDateConverter(), LocalDate.class);
此外,該庫可以理解點狀屬性路徑:
if (post.getMetadata() == null) {
post.setMetadata(new Post.Metadata());
}
BeanUtils.setProperty(post, "metadata.wordCount", 850);
由於嵌套屬性可能為空,因此我們應該在使用BeanUtils
之前初始化它們以避免出現NullPointerException
。
和索引屬性:
BeanUtils.setProperty(post, "tags[0]", "reflection");
4.5. 優雅地處理檢查異常
每個BeanUtils
呼叫都會宣告IllegalAccessException
、 InvocationTargetException
和NoSuchMethodException
。在生產程式碼中,我們會將它們包裝在特定於應用程式的未檢查異常中,以避免服務簽章混亂:
try {
BeanUtils.copyProperties(target, source);
} catch (ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
5. 使用反射時的安全注意事項
反射是一個強大的工具,但它也伴隨著我們不應忽視的安全隱憂:
- 繞過存取控制-如果使用
setAccessible(true)
反射可以存取私人欄位和方法。這可能會破壞封裝並暴露敏感資料。 - 注入風險-如果方法或屬性名稱來自使用者輸入(例如,HTTP 參數、JSON 鍵),攻擊者可能會操縱它們來呼叫非預期的 setter 或 getter
- 批量賦值漏洞– 像
BeanUtils.populate()
這樣的實用程式可以一次設定多個屬性。如果不加以限制,這可能會導致對安全敏感欄位進行未經授權的變更。
6. 結論
Java 反射透過PropertyDescriptor
和 Apache Commons BeanUtils
等工具,使我們能夠以最少的樣板程式碼建立靈活的、元資料驅動的應用程式。在使用必須實現動態行為的框架、序列化器或資料映射層時,這些功能非常寶貴。雖然我們需要處理反射的受檢異常和效能權衡,但這些工具提供的表達能力和解耦能力使它們變得至關重要。無論我們追求的是精準還是便捷,這兩種方法在現代 Java 開發中都能很好地發揮作用。
與往常一樣,程式碼可 在 GitHub 上取得。