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),保證來源引數不會為 null。如果轉換失敗,您的 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 介面。GenericConverter 的簽章比 Converter 更彈性但類型安全性較低,它支援在多個來源類型和目標類型之間進行轉換。此外,GenericConverter 提供來源和目標欄位 Context,您可以在實作轉換邏輯時使用。此類 Context 允許類型轉換由欄位註解或欄位簽章上宣告的泛型資訊驅動。以下清單顯示了 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,或者您可能希望僅在目標類別上定義了特定方法 (例如 static 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 提供了方便的 Factory,用於建立常見的 ConversionService 組態。

組態 ConversionService

ConversionService 是一個無狀態物件,旨在於應用程式啟動時實例化,然後在多個執行緒之間共用。在 Spring 應用程式中,您通常為每個 Spring 容器 (或 ApplicationContext) 組態一個 ConversionService 實例。Spring 會擷取該 ConversionService,並在框架需要執行類型轉換時使用它。您也可以將此 ConversionService 注入到您的任何 Bean 中並直接呼叫它。

如果沒有向 Spring 註冊 ConversionService,則會使用原始的基於 PropertyEditor 的系統。

若要向 Spring 註冊預設 ConversionService,請新增下列 Bean 定義,並將 id 設定為 conversionService

<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 章節中的 轉換和格式化

在某些情況下,您可能希望在轉換期間套用格式化。請參閱 FormatterRegistry SPI,以了解有關使用 FormattingConversionServiceFactoryBean 的詳細資訊。

以程式設計方式使用 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 方法,但它不適用於更複雜的類型,例如參數化元素的集合。例如,如果您想要以程式設計方式將 IntegerList 轉換為 StringList,則需要提供來源類型和目標類型的正式定義。

幸運的是,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-to-String 轉換器。您可以使用 DefaultConversionService 類別上的靜態 addDefaultConverters 方法,向任何 ConverterRegistry 註冊相同的轉換器。

值類型的轉換器會重複用於陣列和集合,因此無需建立特定的轉換器來從 SCollection 轉換為 TCollection,假設標準集合處理是適當的。