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

时间:2022-08-23 19:04:26

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

环境:Springboot2.4.11

Spring3引入了一个core.convert包,它提供了一个通用类型转换系统。系统定义一个SPI来实现类型转换逻辑,定义一个API来在运行时执行类型转换。在Spring容器中,你可以使用此系统作为PropertyEditor实现的替代方案,将外部化的bean属性值字符串转换为所需的属性类型。你还可以在应用程序中需要进行类型转换的任何位置使用公共API。

Converter SPI

实现类型转换逻辑的SPI是简单且强类型的,如以下接口定义所示:

  1. packageorg.springframework.core.convert.converter;
  2. publicinterfaceConverter{
  3. Tconvert(Ssource);
  4. }

要创建自己的转换器,需要实现converter接口,并将S参数化为要转换的类型,将T参数化为要转换的类型。如果需要将S的集合或数组转换为T的集合或集合,还可以透明地应用这样的转换器,前提是同时注册了委托数组或集合转换器(默认情况下,DefaultConversionService会这样做)。

对于每个转换调用,保证源参数source不为null。如果转换失败,转换器可能会抛出任何未检查的异常。具体来说,它应该抛出IllegalArgumentException以报告无效的源值。注意确保转换器实现是线程安全的。

为了方便起见,core.convert.support包中提供了几种转换器实现。其中包括从字符串到数字和其他常见类型的转换器。下表显示了StringToInteger类,它是典型的转换器实现:

  1. packageorg.springframework.core.convert.support;
  2. finalclassStringToIntegerimplementsConverterInteger>{
  3. publicIntegerconvert(Stringsource){
  4. returnInteger.valueOf(source);
  5. }
  6. }

使用ConverterFactory

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

  1. packageorg.springframework.core.convert.converter;
  2. publicinterfaceConverterFactory{
  3. ConvertergetConverter(ClasstargetType);
  4. }

将S参数化为要转换的类型,将R参数化为定义可转换为的类范围的基类型。然后实现getConverter(类),其中T是R的一个子类。

以StringToEnumConverterFactory为例:

  1. packageorg.springframework.core.convert.support;
  2. finalclassStringToEnumConverterFactoryimplementsConverterFactory{
  3. publicConvertergetConverter(ClasstargetType){
  4. returnnewStringToEnumConverter(targetType);
  5. }
  6. privatefinalclassStringToEnumConverterimplementsConverter{
  7. privateClassenumType;
  8. publicStringToEnumConverter(ClassenumType){
  9. this.enumType=enumType;
  10. }
  11. publicTconvert(Stringsource){
  12. return(T)Enum.valueOf(this.enumType,source.trim());
  13. }
  14. }
  15. }

自定义类型转换

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

  1. publicclassUsers{
  2. privateStringname;
  3. privateIntegerage;
  4. }

接口

  1. @GetMapping("/convert2")
  2. publicObjectconvert2(Usersusers){
  3. returnusers;
  4. }

调用接口

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

如上,通过get方式users的参数通过逗号分割。接下来就是写类型转换器了。

  1. @SuppressWarnings({"rawtypes","unchecked"})
  2. publicclassUsersConverterFactoryimplementsConverterFactory{
  3. @Override
  4. publicConvertergetConverter(ClasstargetType){
  5. returnnewStringToUsersConverter();
  6. }
  7. privatefinalclassStringToUsersConverterimplementsConverter{
  8. publicUsersconvert(Stringsource){
  9. if(source==null||source.trim().length()==0){
  10. returnnull;
  11. }
  12. Usersuser=newUsers();
  13. //下面做简单处理,不做校验
  14. String[]values=source.split(",");
  15. user.setName(values[0]);
  16. user.setAge(Integer.parseInt(values[1]));
  17. returnuser;
  18. }
  19. }
  20. }

注册类型转换器

  1. @Configuration
  2. publicclassWebConfigimplementsWebMvcConfigurer{
  3. @Override
  4. publicvoidaddFormatters(FormatterRegistryregistry){
  5. registry.addConverterFactory(newUsersConverterFactory());
  6. }
  7. }

编程方式使用类型转换器

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

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

  1. publicenumPayStatus{
  2. START,PROCESS,COMPLETE
  3. }
  1. @RestController
  2. @RequestMapping("/users")
  3. publicclassUsersController{
  4. @Resource
  5. privateConversionServicecs;
  6. @GetMapping("/convert")
  7. publicObjectconvert(Stringstatus){
  8. booleancanConvert=cs.canConvert(String.class,PayStatus.class);
  9. returncanConvert?cs.convert(status,PayStatus.class):"UNKNOW";
  10. }
  11. }

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

类型转换的实现原理

以自定义类型转换器为例

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

查找参数解析器

查找合适的HandlerMethodArgumentResolver

  1. publicclassInvocableHandlerMethodextendsHandlerMethod{
  2. protectedObject[]getMethodArgumentValues(...)throwsException{
  3. //查找合适的参数解析器(本例应用的是ServletModelAttributeMethodProcessor)
  4. if(!this.resolvers.supportsParameter(parameter)){
  5. thrownewIllegalStateException(formatArgumentError(parameter,"Nosuitableresolver"));
  6. }
  7. try{
  8. args[i]=this.resolvers.resolveArgument(parameter,mavContainer,request,this.dataBinderFactory);
  9. }
  10. }
  11. }

解析参数

执行

  1. publicclassModelAttributeMethodProcessorimplementsHandlerMethodArgumentResolver{
  2. publicfinalObjectresolveArgument(...){
  3. attribute=createAttribute(name,parameter,binderFactory,webRequest);
  4. }
  5. }
  6. publicclassServletModelAttributeMethodProcessorextendsModelAttributeMethodProcessor{
  7. protectedfinalObjectcreateAttribute(StringattributeName,MethodParameterparameter,WebDataBinderFactorybinderFactory,NativeWebRequestrequest)throwsException{
  8. //这里得到的是原始值
  9. Stringvalue=getRequestValueForAttribute(attributeName,request);
  10. if(value!=null){
  11. Objectattribute=createAttributeFromRequestValue(value,attributeName,parameter,binderFactory,request);
  12. if(attribute!=null){
  13. returnattribute;
  14. }
  15. }
  16. returnsuper.createAttribute(attributeName,parameter,binderFactory,request);
  17. }
  18. protectedObjectcreateAttributeFromRequestValue(StringsourceValue,StringattributeName,MethodParameterparameter,WebDataBinderFactorybinderFactory,NativeWebRequestrequest)throwsException{
  19. DataBinderbinder=binderFactory.createBinder(request,null,attributeName);
  20. //ConversionService对象是在容器启动的时候就初始化好的
  21. //在WebMvcAutoConfiguration#mvcConversionService方法中初始化。
  22. ConversionServiceconversionService=binder.getConversionService();
  23. if(conversionService!=null){
  24. TypeDescriptorsource=TypeDescriptor.valueOf(String.class);
  25. TypeDescriptortarget=newTypeDescriptor(parameter);
  26. //判断是否有合适的类型转换器
  27. if(conversionService.canConvert(source,target)){
  28. //此方法中进行类型的转换
  29. returnbinder.convertIfNecessary(sourceValue,parameter.getParameterType(),parameter);
  30. }
  31. }
  32. returnnull;
  33. }
  34. }

原文链接:https://www.toutiao.com/a7015115159660200486/