环境:Springboot2.4.11
Spring3引入了一个core.convert包,它提供了一个通用类型转换系统。系统定义一个SPI来实现类型转换逻辑,定义一个API来在运行时执行类型转换。在Spring容器中,你可以使用此系统作为PropertyEditor实现的替代方案,将外部化的bean属性值字符串转换为所需的属性类型。你还可以在应用程序中需要进行类型转换的任何位置使用公共API。
实现类型转换逻辑的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,如下例所示:
复制
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.
调用接口
如上,通过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.