Objenesis 簡介
1. 簡介
Java 中的物件建立過程最常涉及建構函數的執行。然而,在某些情況下,我們可能需要建立物件而不強制執行建構函數邏輯。
例如,我們可能希望在單元測試期間模擬依賴關係或在框架內管理物件生命週期。其他範例是處理具有複雜建構函數依賴關係的遺留程式碼或在反序列化的特定場景中。
為了幫助我們處理此類用例,我們有Objenesis——一個小型 Java 函式庫,它允許我們從類別實例化物件而無需呼叫它們的建構子。它對於需要動態建立物件、完全繞過建構函式執行的函式庫和框架很有用。
在本教程中,我們將透過實際範例探索 Objenesis 的工作原理、如何在我們的專案中使用它以及它的實際應用。
2. Java 中的傳統物件創建
我們知道,當我們使用new
關鍵字建立物件時,Java 總是會執行其中一個建構子。
讓我們建立User
類別來說明 Java 中這個標準物件的建立:
public class User {
private String name;
public User() {
System.out.println("User constructor is called!");
}
// getters and setters
}
這裡,我們在User
類別的預設建構函式中包含了一個列印語句,使我們能夠確認在建立User
類別的實例時呼叫了建構子:
User user = new User();
執行此程式碼後,我們可以觀察到訊息User constructor is called!
印在控制台上,從而確認建構函式確實用於建立User
物件。
3. Objenesis 如何運作?
Objenesis 不依賴new
關鍵字,然後呼叫類別的建構子之一來建立物件。相反,它使用低階 JVM 機制來分配記憶體並實例化對象,同時完全繞過建構子。
在內部,Objenesis 會根據 JVM 供應商和版本嘗試不同的實例化策略,直到成功為止。
3.1.設置
我們將最新的org.objenesis
Maven 依賴項加入我們的pom.xml
中:
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>3.4</version>
</dependency>
該函式庫為Objenesis
介面提供了兩種主要實作:
-
ObjenesisStd
:標準的線程安全實現,在創建物件時嘗試不同的策略(依賴 JVM 的供應商和版本) -
[ObjenesisSerializer](https://javadoc.io/static/org.objenesis/objenesis/3.4/org/objenesis/ObjenesisSerializer.html)
:針對序列化框架優化的專門的非線程安全實現
此外,Objenesis 提供了ObjenesisHelper
實用程式類,可使用上述實作簡化物件實例化。
3.2.使用ObjenesisStd
建立對象
讓我們修改我們的User
類別來完全限制建構函式的使用:
public class User implements Serializable {
private String name;
public User() {
throw new RuntimeException("User constructor should not be called!");
}
// getters and setters
}
在這裡,我們在預設建構函式中拋出了一個RuntimeException
,以確保它永遠不會在我們的測試中被呼叫。
然後,讓我們使用ObjenesisStd
類別來建立User
類別的對象,而不呼叫建構子:
Objenesis objenesis = new ObjenesisStd();
User user = objenesis.newInstance(User.class);
assertNotNull(user);
user.setName("Harry Potter");
assertEquals("Harry Potter", user.getName());
在這裡,我們創建了ObjenesisStd
類別的一個實例,它是 Objenesis 的一個線程安全實作。然後,我們使用它來實例化User
對象,而無需呼叫其建構函數。
assertNotNull()
檢查確保物件已成功建立。
為了確認物件行為正常,我們使用setName()
方法設定使用者的name
,並使用getName()
方法檢索它,確認其屬性和行為保持不變。
3.3.使用ObjenesisSerializer
建立對象
類似地,我們可以利用ObjenesisSerializer
類別透過基於序列化的實例化策略來建立User
物件:
Objenesis objenesis = new ObjenesisSerializer();
User user = objenesis.newInstance(User.class);
assertNotNull(user);
user.setName("Harry Potter");
assertEquals("Harry Potter", user.getName());
Objenesis
介面的這個實作使用類似於反序列化的方法來創建新物件。
底層物件創建機制依賴序列化原則。因此,當使用ObjenesisSerializer
建立物件時,我們應該確保我們的類別實作了Serializable
介面。否則,很可能會導致異常。
3.4.使用ObjenesisHelper
創建對象
此外, Objenesis 函式庫提供了ObjenesisHelper
實用程式類,它使用標準和序列化策略簡化了物件創建。
讓我們來研究一下其標準策略的使用:
User user = ObjenesisHelper.newInstance(User.class);
assertNotNull(user);
user.setName("Harry Potter");
assertEquals("Harry Potter", user.getName());
同樣的,我們可以使用序列化策略:
User user = ObjenesisHelper.newSerializableInstance(User.class);
assertNotNull(user);
user.setName("Harry Potter");
assertEquals("Harry Potter", user.getName());
因此, ObjenesisHelper
類別提供了一種方便且首選的方式來使用 Objenesis,因為它封裝了針對常見用例的ObjenesisStd
或ObjenesisSerializer
實例的明確創建。
4.高階用例
Objenesis 支援許多高階用例,尤其是在標準物件建立不可行時。
序列化框架(例如 Kryo)在內部使用 Objenesis 在反序列化期間創建對象,而無需調用其建構函數。這對於效能以及處理序列化資料不符合建構函式要求的情況至關重要。
模擬框架(例如 Mockito 和 EasyMock)通常在內部利用 Objenesis 來建立模擬物件或代理以用於測試目的。當我們建立一個類別的模擬時,模擬框架需要實例化一個可以攔截方法呼叫和記錄互動的代理物件。 Objenesis 提供了一種乾淨的方法來建立這些代理實例,而無需觸發原始類別的潛在複雜或依賴性建構子。
通常, final
類別或具有private
建構函數的類別極難反射實例化。 Objenesis 繞過了這個限制,即使建構子是private
或類別是final
,也可以實例化.
一些輕量級或自訂的依賴注入框架可能會使用 Objenesis 來實例化 bean 或服務類,其中建構函式註入不切實際或不可取,允許基於屬性的注入而不呼叫建構子。
在效能至關重要的應用程式中,我們可能希望複製或重複使用對象,而不必支付完全重新初始化的成本。 Objenesis 可以幫助創建一個我們手動填充的新實例,跳過昂貴的建構函數邏輯。
5.最佳實踐
Objenesis 提供了一種方便的物件實例化替代方法,無需涉及建構函數。然而,建議謹慎使用它並了解其含義。
過度使用或誤用可能會導致意外行為和可維護性挑戰。讓我們來看看一些推薦的最佳實踐:
- 優先使用建構函式:我們應該利用建構函式來建立常規對象,並且僅在必要時使用 Objenesis 來繞過建構函式調用
- 我們應該注意安全限制:Java 安全管理器或容器化應用程式等環境可能會因為 Objenesis 的低階物件實例化機製而阻止它
- 利用框架與 Objenesis 的整合:當使用 Mockito 或 Kryo 等框架時,我們應該讓它們在內部處理 Objenesis,而不是直接呼叫它
- 未初始化物件的處理:由於 Objenesis 跳過了建構函式邏輯,因此我們在處理預設初始化欄位時應更加小心,因為如果處理不當,它們可能會導致意外行為
- 測試實例化物件:我們必須確保使用 Objenesis 建立的物件經過徹底測試,以確認所有屬性和行為保持完整
- 清楚地記錄其用法:我們應該記錄 Objenesis 的使用方法和相關的物件初始化策略,以提高其他開發人員的可維護性
6. 結論
在本文中,我們探討了 Objenesis 函式庫,它允許我們在不呼叫建構函數的情況下創建對象,這使其對於序列化、模擬和代理框架很有用。
我們探索了它的工作原理,在專案中設定了它,並透過真實的例子了解了它的功能。然而,儘管 Objenesis 功能強大,但我們應該只在必要時使用它,以避免產生意想不到的副作用。此外,我們還研究了一些高級用例和最佳實踐。
與往常一樣,本文的程式碼可在 GitHub 上找到。