Spring 型別轉換
core.convert
包提供了通用的型別轉換系統。該系統定義了一個 SPI 用於實現型別轉換邏輯,以及一個 API 用於在執行時執行型別轉換。在 Spring 容器中,你可以使用該系統作為 PropertyEditor
實現的替代方案,將外部化的 bean 屬性值字串轉換為所需的屬性型別。你也可以在應用程式中任何需要型別轉換的地方使用公共 API。
Converter SPI
用於實現型別轉換邏輯的 SPI 簡單且型別安全,如下面的介面定義所示
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
要建立自己的轉換器,請實現 Converter
介面,並將 S
引數化為要從轉換的型別,將 T
引數化為要轉換為的型別。如果需要將 S
的集合或陣列轉換為 T
的陣列或集合,只要註冊了委託陣列或集合轉換器(DefaultConversionService
預設會註冊),此類轉換器也可以透明地應用。
對於每次呼叫 convert(S)
,保證源引數不為空。如果轉換失敗,你的 Converter
可以丟擲任何非受檢異常。特別是,它應該丟擲 IllegalArgumentException
以報告無效的源值。請注意確保你的 Converter
實現是執行緒安全的。
core.convert.support
包中提供了一些轉換器實現以方便使用。其中包括字串到數字和其他常見型別的轉換器。下面的列表顯示了 StringToInteger
類,它是一個典型的 Converter
實現
package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
使用 ConverterFactory
當需要集中處理整個類層次結構的轉換邏輯時(例如,將 String
轉換為 Enum
物件時),可以實現 ConverterFactory
,如下面的示例所示
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
將 S 引數化為要從轉換的型別,將 R 引數化為定義可以轉換為的類的範圍的基礎型別。然後實現 getConverter(Class<T>)
,其中 T 是 R 的子類。
考慮將 StringToEnumConverterFactory
作為示例
package org.springframework.core.convert.support;
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}
使用 GenericConverter
當需要複雜的 Converter
實現時,請考慮使用 GenericConverter
介面。與 Converter
相比,GenericConverter
具有更靈活但型別不那麼嚴格的簽名,支援在多種源型別和目標型別之間進行轉換。此外,GenericConverter
提供源欄位和目標欄位的上下文,可在實現轉換邏輯時使用。此類上下文允許根據欄位註解或欄位簽名上宣告的泛型資訊來驅動型別轉換。下面的列表顯示了 GenericConverter
的介面定義
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
要實現 GenericConverter
,讓 getConvertibleTypes()
返回支援的源→目標型別對。然後實現 convert(Object, TypeDescriptor, TypeDescriptor)
以包含你的轉換邏輯。源 TypeDescriptor
提供對儲存要轉換的值的源欄位的訪問。目標 TypeDescriptor
提供對要設定轉換後值的目標欄位的訪問。
GenericConverter
的一個很好的示例是 Java 陣列和集合之間的轉換器。此類 ArrayToCollectionConverter
會檢查宣告目標集合型別的欄位,以解析集合的元素型別。這使得源陣列中的每個元素在集合被設定到目標欄位之前被轉換為集合元素型別。
由於 GenericConverter 是一個更復雜的 SPI 介面,只有在你需要時才應該使用它。對於基本的型別轉換需求,優先選擇 Converter 或 ConverterFactory 。 |
使用 ConditionalGenericConverter
有時,你希望 Converter
僅在特定條件成立時執行。例如,你可能希望 Converter
僅在目標欄位上存在特定註解時執行,或者僅在目標類上定義了特定方法(例如靜態 valueOf
方法)時執行 Converter
。ConditionalGenericConverter
是 GenericConverter
和 ConditionalConverter
介面的並集,允許你定義此類自定義匹配條件
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
ConditionalGenericConverter
的一個很好的示例是 IdToEntityConverter
,它在持久化實體識別符號和實體引用之間進行轉換。此類 IdToEntityConverter
可能僅在目標實體型別聲明瞭靜態查詢方法(例如 findAccount(Long)
)時才匹配。你可以在 matches(TypeDescriptor, TypeDescriptor)
的實現中執行此類查詢方法檢查。
ConversionService
API
ConversionService
定義了一個統一的 API,用於在執行時執行型別轉換邏輯。轉換器通常在以下外觀介面後面執行
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
大多數 ConversionService
實現還實現了 ConverterRegistry
,它提供了一個用於註冊轉換器的 SPI。在內部,ConversionService
實現委託給其註冊的轉換器來執行型別轉換邏輯。
core.convert.support
包中提供了一個健壯的 ConversionService
實現。GenericConversionService
是適用於大多數環境的通用實現。ConversionServiceFactory
提供了一個方便的工廠,用於建立常見的 ConversionService
配置。
配置 ConversionService
ConversionService
是一個無狀態物件,設計用於在應用程式啟動時例項化,然後在多個執行緒之間共享。在 Spring 應用中,你通常為每個 Spring 容器(或 ApplicationContext
)配置一個 ConversionService
例項。Spring 會獲取該 ConversionService
並在框架需要執行型別轉換時使用它。你也可以將此 ConversionService
注入到你的任何 bean 中並直接呼叫它。
如果沒有向 Spring 註冊 ConversionService ,將使用原始的基於 PropertyEditor 的系統。 |
要向 Spring 註冊一個預設的 ConversionService
,新增以下 id 為 conversionService
的 bean 定義
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
預設的 ConversionService
可以轉換字串、數字、列舉、集合、Map 和其他常見型別。要用你自己的自定義轉換器補充或覆蓋預設轉換器,請設定 converters
屬性。屬性值可以實現 Converter
、ConverterFactory
或 GenericConverter
介面中的任何一個。
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
在 Spring MVC 應用中使用 ConversionService
也很常見。請參閱 Spring MVC 章中的轉換和格式化。
在某些情況下,你可能希望在轉換期間應用格式化。有關使用 FormattingConversionServiceFactoryBean
的詳細資訊,請參閱FormatterRegistry
SPI。
程式設計式使用 ConversionService
要程式設計式地使用 ConversionService
例項,你可以像注入任何其他 bean 一樣注入對它的引用。以下示例顯示瞭如何操作
-
Java
-
Kotlin
@Service
public class MyService {
private final ConversionService conversionService;
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void doIt() {
this.conversionService.convert(...)
}
}
@Service
class MyService(private val conversionService: ConversionService) {
fun doIt() {
conversionService.convert(...)
}
}
對於大多數用例,你可以使用指定 targetType
的 convert
方法,但它不適用於更復雜的型別,例如引數化元素的集合。例如,如果要程式設計式地將 Integer
列表轉換為 String
列表,則需要提供源型別和目標型別的正式定義。
幸運的是,TypeDescriptor
提供了各種選項來使操作變得簡單,如下面的示例所示
-
Java
-
Kotlin
DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
val cs = DefaultConversionService()
val input: List<Integer> = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java)))
請注意,DefaultConversionService
會自動註冊適用於大多數環境的轉換器。這包括集合轉換器、標量轉換器和基本的 Object
到 String
轉換器。你可以使用 DefaultConversionService
類上的靜態 addDefaultConverters
方法將相同的轉換器註冊到任何 ConverterRegistry
中。
值型別的轉換器會重用於陣列和集合,因此假設標準集合處理是合適的,則無需建立特定的轉換器來將 S
集合轉換為 T
集合。