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 介面,只有在你需要時才應該使用它。對於基本的型別轉換需求,優先選擇 ConverterConverterFactory

使用 ConditionalGenericConverter

有時,你希望 Converter 僅在特定條件成立時執行。例如,你可能希望 Converter 僅在目標欄位上存在特定註解時執行,或者僅在目標類上定義了特定方法(例如靜態 valueOf 方法)時執行 ConverterConditionalGenericConverterGenericConverterConditionalConverter 介面的並集,允許你定義此類自定義匹配條件

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 屬性。屬性值可以實現 ConverterConverterFactoryGenericConverter 介面中的任何一個。

<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(...)
	}
}

對於大多數用例,你可以使用指定 targetTypeconvert 方法,但它不適用於更復雜的型別,例如引數化元素的集合。例如,如果要程式設計式地將 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 會自動註冊適用於大多數環境的轉換器。這包括集合轉換器、標量轉換器和基本的 ObjectString 轉換器。你可以使用 DefaultConversionService 類上的靜態 addDefaultConverters 方法將相同的轉換器註冊到任何 ConverterRegistry 中。

值型別的轉換器會重用於陣列和集合,因此假設標準集合處理是合適的,則無需建立特定的轉換器來將 S 集合轉換為 T 集合。