1. 概述
多態化反序列化是 Jackson 的一項特性,Jackson 是一個流行的 Java JSON 序列化和反序列化庫。它允許我們將 JSON 反序列化為 Java 對象層次結構,即使在編譯時未知道具體的類型。當您擁有父類和多個子類時,這種特性非常有用,我們可以確定反序列化過程中的對象實際類型,而不會丟失對象多態性的信息。
在本教程中,我們將探索如何通過兩種方式實現這一點:使用類型處理註解來指示父類子類型的基類,或使用 Reflections 方式掃描並註冊所有子類型。
2. 使用 @JsonTypeInfo 和 @JsonSubTypes 進行多態化反序列化
Jackson 的多態化類型處理註解是其中一種最直接的方法。
讓我們看一個實現示例,其中我們使用 @JsonTypeInfo 和 @JsonSubTypes 來指示 Vehicle 實體及其子類型,並基於現有屬性進行反序列化:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Vehicle.ElectricVehicle.class, name = "ELECTRIC_VEHICLE"),
@JsonSubTypes.Type(value = Vehicle.FuelVehicle.class, name = "FUEL_VEHICLE")
})
public class Vehicle {
public String type;
// standard setters and getters
public static class ElectricVehicle extends Vehicle {
String autonomy;
String chargingTime;
// standard setters and getters
}
public static class FuelVehicle extends Vehicle {
String fuelType;
String transmissionType;
// standard setters and getters
}
}現在,讓我們看看如何將 JSON 輸入反序列化為 Vehicle 類型的子類的工作方式:
@Test
public void whenDeserializingPolymorphic_thenCorrect() throws JsonProcessingException {
String json = "{\"type\":\"ELECTRIC_VEHICLE\",\"autonomy\":\"500\",\"chargingTime\":\"200\"}";
Vehicle vehicle = new ObjectMapper().readerFor(Vehicle.class).readValue(json);
assertEquals(Vehicle.ElectricVehicle.class, vehicle.getClass());
}3. 使用 @JsonTypeInfo 和 Reflections 進行多態化反序列化以註冊子類型
接下來,讓我們探索一種通過創建自定義註解並使用 Reflections 庫 進行掃描和註冊所有現有子類型的不同方法。
3.1 反射介紹
反射是一種強大的特性,允許 Java 程序在運行時檢查或操縱其結構和行為。這非常實用,因為我們可以創建一個自定義註解來指示每個子類型的類型名稱,並使用 Reflections 來識別和註冊它們。
我們的 Reflections 庫指南會更詳細地描述它,以及它的使用案例。
3.2. Maven 依賴
首先,要使用 Reflections,我們需要添加 reflections 依賴:
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>我們可以在 Maven Central 倉庫 中找到其最新版本。
3.3. 創建自定義標註以指示子類型的類型名稱
Java 標註是一種元數據形式,可在編譯時或運行時提供有關類、方法、字段和其他程序元素的附加信息。它們不會直接影響代碼的邏輯,而是向編譯器或運行時環境提供指令或詳細信息。
關於自定義標註的更多信息,請參閲我們在 Java 中創建自定義標註的文章。
為了指示每個 Vehicle 子類型的類型名稱,我們將創建一個以下標註,具有運行時可見性,並適用於類型(類):
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface VehicleSubType {
String value();
}現在,讓我們看看如何更新現有代碼以指定每個子類定義的類型:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
public class Vehicle {
public String type;
// standard setters and getters
@VehicleSubType("ELECTRIC_VEHICLE")
public static class ElectricVehicle extends Vehicle {
String autonomy;
String chargingTime;
// standard setters and getters
}
@VehicleSubType("FUEL_VEHICLE")
public static class FuelVehicle extends Vehicle {
String fuelType;
String transmissionType;
// standard setters and getters
}
}請注意,我們仍然需要使用 @JsonTypeInfo 註解來指定用於存儲類型信息的屬性,在父類上。
3.4. 使用反射註冊子類型
最後,我們需要自定義 Jackson 的 ObjectMapper 以將帶有自定義註解的類註冊為子類型。
我們將首先識別所有帶有自定義註解 @VehicleSubType 的類。 隨後,對於每個找到的類,我們可以提取註解的值,並使用關聯的類型名稱註冊子類型:
private ObjectMapper getCustomObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
Reflections reflections = new Reflections("com.baeldung.jackson.polymorphicdeserialization.reflection");
Set<Class<?>> subtypes = reflections.getTypesAnnotatedWith(VehicleSubType.class);
for (Class<?> subType : subtypes) {
VehicleSubType annotation = subType.getAnnotation(VehicleSubType.class);
if (annotation != null) {
String typeName = annotation.value();
objectMapper.registerSubtypes(new NamedType(subType, typeName));
}
}
return objectMapper;
}現在,讓我們使用之前相同的輸入測試我們的代碼:
@Test
public void whenDeserializingPolymorphic_thenCorrect() throws JsonProcessingException {
String json = "{\"type\":\"ELECTRIC_VEHICLE\",\"autonomy\":\"500\",\"chargingTime\":\"200\"}";
ObjectMapper objectMapper = getCustomObjectMapper();
Vehicle vehicle = objectMapper.readValue(json, Vehicle.class);
assertEquals(Vehicle.ElectricVehicle.class, vehicle.getClass());
}4. 兩種方法的差異
使用 @JsonSubTypes 的方法提供顯式配置,通過註解定義子類型及其類型名稱。 這提供了一個集中且清晰的層次結構,確保編譯時安全。
基於 Reflections 的註冊允許在運行時動態發現子類型。 雖然它減少了樣板代碼,但引入了運行時開銷,並且缺乏編譯時安全,並且需要外部依賴進行類路徑掃描。 但是,這種方法在處理大量子類型時可能 適用,因為添加一個新的子類型不會影響已有的代碼。
5. 結論
在本文中,我們探討了兩種不同的方法,重點介紹了使用自定義標註和Reflections來識別和註冊子類型。總而言之,選擇哪一種方法取決於應用程序的特定需求。如果項目的子類型已知且穩定,@JsonSubTypes提供了一種更健壯和安全的選項。相反,基於Reflections的註冊可能更適合對靈活性和運行時適應性要求較高的項目。