Springboot强大的类型转换功能,你必须要掌握

Spring3引入了一个core.convert包,它提供了一个通用类型转换系统。系统定义一个SPI来实现类型转换逻辑,定义一个API来在运行时执行类型转换。
首页 新闻资讯 行业资讯 Springboot强大的类型转换功能,你必须要掌握

[[430214]]

环境:Springboot2.4.11

Spring3引入了一个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); }
  • 1.

  • 2.

  • 3.

  • 4.

要创建自己的转换器,需要实现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);   } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

使用ConverterFactory

当需要集中整个类层次结构的转换逻辑时(例如,从字符串转换为枚举对象时),可以实现ConverterFactory,如下例所示:

复制

package org.springframework.core.convert.converter; public interface ConverterFactory<S, R> {   <T extends R> Converter<S, T> getConverter(Class<T> targetType); }
  • 1.

  • 2.

  • 3.

  • 4.

将S参数化为要转换的类型,将R参数化为定义可转换为的类范围的基类型。然后实现getConverter(类),其中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());     }   } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

自定义类型转换

现在需要将接受的字符串转换为Users对象

复制

public class Users {   private String name ;   private Integer age ; }
  • 1.

  • 2.

  • 3.

  • 4.

接口

复制

@GetMapping("/convert2") public Object convert2(Users users) {   return users ;  }
  • 1.

  • 2.

  • 3.

  • 4.

调用接口

Springboot强大的类型转换功能你必须要掌握

如上,通过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 ;     }   } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

 注册类型转换器

复制

@Configuration public class WebConfig implements WebMvcConfigurer {    @Override   public void addFormatters(FormatterRegistry registry) {     registry.addConverterFactory(new UsersConverterFactory()) ;   }      }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

编程方式使用类型转换器

要以编程方式使用ConversionService实例,可以像对任何其他bean一样向其注入引用。以下示例显示了如何执行此操作:

我们使用系统内置的类型转换器:字符串类型转枚举类型

复制

public enum PayStatus {        START, PROCESS, COMPLETE      }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

复制

@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";    }    }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

先判断是否能够转换,其实就是判断有没有从source到target的类型转换器存在。

类型转换的实现原理

以自定义类型转换器为例

SpringMVC在进行接口调用是会执行相应的参数解析,确定了参数解析器后会执行转换服务。

查找参数解析器

查找合适的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);     }         } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

解析参数

执行

复制

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;   } }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

  • 32.

  • 33.

  • 34.

 

22    2021-10-21 08:13:11    Springboot