Springboot強大的類型轉換功能,你必須要掌握
環(huán)境:Springboot2.4.11
Spring3引入了一個core.convert包,它提供了一個通用類型轉換系統(tǒng)。系統(tǒng)定義一個SPI來實現類型轉換邏輯,定義一個API來在運行時執(zhí)行類型轉換。在Spring容器中,你可以使用此系統(tǒng)作為PropertyEditor實現的替代方案,將外部化的bean屬性值字符串轉換為所需的屬性類型。你還可以在應用程序中需要進行類型轉換的任何位置使用公共API。
Converter SPI
實現類型轉換邏輯的SPI是簡單且強類型的,如以下接口定義所示:
- package org.springframework.core.convert.converter;
- public interface Converter<S, T> {
- T convert(S source);
- }
要創(chuàng)建自己的轉換器,需要實現converter接口,并將S參數化為要轉換的類型,將T參數化為要轉換的類型。如果需要將S的集合或數組轉換為T的集合或集合,還可以透明地應用這樣的轉換器,前提是同時注冊了委托數組或集合轉換器(默認情況下,DefaultConversionService會這樣做)。
對于每個轉換調用,保證源參數source不為null。如果轉換失敗,轉換器可能會拋出任何未檢查的異常。具體來說,它應該拋出IllegalArgumentException以報告無效的源值。注意確保轉換器實現是線程安全的。
為了方便起見,core.convert.support包中提供了幾種轉換器實現。其中包括從字符串到數字和其他常見類型的轉換器。下表顯示了StringToInteger類,它是典型的轉換器實現:
- package org.springframework.core.convert.support;
- final class StringToInteger implements Converter<String, Integer> {
- public Integer convert(String source) {
- return Integer.valueOf(source);
- }
- }
使用ConverterFactory
當需要集中整個類層次結構的轉換邏輯時(例如,從字符串轉換為枚舉對象時),可以實現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(類
以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());
- }
- }
- }
自定義類型轉換
現在需要將接受的字符串轉換為Users對象
- public class Users {
- private String name ;
- private Integer age ;
- }
接口
- @GetMapping("/convert2")
- public Object convert2(Users users) {
- return users ;
- }
調用接口

如上,通過get方式users的參數通過逗號分割。接下來就是寫類型轉換器了。
- @SuppressWarnings({"rawtypes", "unchecked"})
- public class UsersConverterFactory implements ConverterFactory<String, Users> {
- @Override
- public <T extends Users> Converter<String, T> getConverter(Class<T> targetType) {
- return new StringToUsersConverter() ;
- }
- private final class StringToUsersConverter<T extends Users> implements Converter<String, Users> {
- public Users convert(String source) {
- if (source == null || source.trim().length() == 0) {
- return null ;
- }
- Users user = new Users() ;
- // 下面做簡單處理,不做校驗
- String[] values = source.split(",") ;
- user.setName(values[0]) ;
- user.setAge(Integer.parseInt(values[1]));
- return user ;
- }
- }
- }
注冊類型轉換器
- @Configuration
- public class WebConfig implements WebMvcConfigurer {
- @Override
- public void addFormatters(FormatterRegistry registry) {
- registry.addConverterFactory(new UsersConverterFactory()) ;
- }
- }
編程方式使用類型轉換器
要以編程方式使用ConversionService實例,可以像對任何其他bean一樣向其注入引用。以下示例顯示了如何執(zhí)行此操作:
我們使用系統(tǒng)內置的類型轉換器:字符串類型轉枚舉類型
- public enum PayStatus {
- START, PROCESS, COMPLETE
- }
- @RestController
- @RequestMapping("/users")
- public class UsersController {
- @Resource
- private ConversionService cs ;
- @GetMapping("/convert")
- public Object convert(String status) {
- boolean canConvert = cs.canConvert(String.class, PayStatus.class) ;
- return canConvert ? cs.convert(status, PayStatus.class) : "UNKNOW";
- }
- }
先判斷是否能夠轉換,其實就是判斷有沒有從source到target的類型轉換器存在。
類型轉換的實現原理
以自定義類型轉換器為例
SpringMVC在進行接口調用是會執(zhí)行相應的參數解析,確定了參數解析器后會執(zhí)行轉換服務。
查找參數解析器
查找合適的HandlerMethodArgumentResolver
- public class InvocableHandlerMethod extends HandlerMethod {
- protected Object[] getMethodArgumentValues(...) throws Exception {
- // 查找合適的參數解析器(本例應用的是ServletModelAttributeMethodProcessor)
- if (!this.resolvers.supportsParameter(parameter)) {
- throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
- }
- try {
- args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
- }
- }
- }
解析參數
執(zhí)行
- public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver {
- public final Object resolveArgument(...) {
- attribute = createAttribute(name, parameter, binderFactory, webRequest);
- }
- }
- public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
- protected final Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
- // 這里得到的是原始值
- String value = getRequestValueForAttribute(attributeName, request);
- if (value != null) {
- Object attribute = createAttributeFromRequestValue(value, attributeName, parameter, binderFactory, request);
- if (attribute != null) {
- return attribute;
- }
- }
- return super.createAttribute(attributeName, parameter, binderFactory, request);
- }
- protected Object createAttributeFromRequestValue(String sourceValue, String attributeName,MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
- DataBinder binder = binderFactory.createBinder(request, null, attributeName);
- // ConversionService對象是在容器啟動的時候就初始化好的
- // 在WebMvcAutoConfiguration#mvcConversionService方法中初始化。
- ConversionService conversionService = binder.getConversionService();
- if (conversionService != null) {
- TypeDescriptor source = TypeDescriptor.valueOf(String.class);
- TypeDescriptor target = new TypeDescriptor(parameter);
- // 判斷是否有合適的類型轉換器
- if (conversionService.canConvert(source, target)) {
- // 此方法中進行類型的轉換
- return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter);
- }
- }
- return null;
- }
- }