或许大家对 Spring 所供给的类型转化才能没有显着的感知,但它一直在默默地为我们服务着,正所谓“润物细无声”。笔者这儿梳理了一些常用的类型转化场景,如下所示:
-
当经过
@RequestParam boolean status
来接纳 HTTP 恳求参数中的status = on
时,字符串类型的 on 能够主动转化为布尔类型的 true; -
当经过
@RequestHeader User user
来接纳 HTTP 恳求头中的userId
时,字符串类型的 userId 能够主动转化为 User 类型; -
当经过
@PathVariable LocalDate begin
来接纳 HTTP 恳求途径中的2023-03-31
时,字符串类型的 2023-03-31 能够主动转化为 LocalDate 类型; -
当经过
@ConfigurationProperties 注解标注的 Bean
来接纳外部装备源中的90s
时,字符串类型的 90s 能够主动转化为 Duration 类型; -
当经过
@ConfigurationProperties 注解标注的 Bean
来接纳外部装备源中的2023-03-31T12:13:14
(有必要是 ISO_8601 格局,否则类型转化将报错) 时,字符串类型的 2023-03-31T12:13:14 能够主动转化为 LocalDateTime 类型。
在 Spring 3.0 之前,类型转化主要由PropertyEditor
担任,Spring 内置了 40+ 个完成类,假如想自定义一个 PropertyEditor,只需求直接承继PropertyEditorSupport
同时掩盖 setAsText() 与 getAsText() 这俩办法即可;PropertyEditor 存在两个显着的缺点,一是只能完成字符串类型与其他类型之间的转化,二是类型转化后的值由成员变量value
来承载,这也就导致 PropertyEditor 是线程不安全的。
从 Sprnig 3.0 开端,类型转化领域迎来了三位新的王者,分别是:Converter
、ConverterFactory
和GenericConverter
,Converter 用于完成1对1 (1:1) 类型转化,ConverterFactory 用于完成一对多 (1:N) 类型转化,GenericConverter 用于完成多对多类 (N:N) 型转化;同时,Spring 针对这些 Converter 供给了一致的访问进口,即ConversionService
,假如大家需求凭借 Converter 来完成线程安全的类型转化,那直接将 ConversionService 注入到目标 Bean 中即可。
为了保持向后兼容,PropertyEditor 仍然沿用至今,也便是说 Spring 中的类型转化体系主要由 PropertyEditor 和 Converter 三兄弟构成。为了屏蔽这两种类型转化体系的内部差异,然后给开发人员供给一致的编程模型,Spring 为大家带来了TypeConverterDelegate
,主要内容如下所示。
public class TypeConverterDelegate {
private final PropertyEditorRegistrySupport propertyEditorRegistry;
private final Object targetObject;
public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry,
Object targetObject) {
this.propertyEditorRegistry = propertyEditorRegistry;
this.targetObject = targetObject;
}
public <T> T convertIfNecessary(String propertyName, Object oldValue,
Object newValue, Class<T> requiredType) throws IllegalArgumentException {
return convertIfNecessary(propertyName, oldValue, newValue, requiredType, TypeDescriptor.valueOf(requiredType));
}
public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue,
Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// Custom editor for this type?
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
// No custom editor but custom ConversionService specified?
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
} catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}
Object convertedValue = newValue;
// Value not of required type?
// 这儿忽略长串的逻辑,感兴趣的读者自行阅览
return (T) convertedValue;
}
}
从上述内容来看,TypeConverterDelegate 首先会获取适宜的 PropertyEditor,然后派遣其进行类型转化;假如未找到合适的 PropertyEditor,则终将派遣 ConversionService 来完成类型转化作业。Spring 在 TypeConverterDelegate 与 PropertyEditor、ConversionService 之间加了一层,也便是 PropertyEditorRegistrySupport,这样灵活性会好一点。
下面用一幅图来描绘:Spring 中类型转化体系的应用场景与派遣模型。
BeanWrapper
与DataBinder
都从属于TypeConverter
,它们针对 Java Bean 都供给了特点访问与特点设定的办法;BeanWrapper 与 DataBinder 并不是一个 Spring Bean,需求的时候直接实例化即可,在实例化过程中会注册一些默认的 PropertyEditor。在 Spring 内部,BeanWrapper 主要活泼于 Bean 加载流程中的特点填充阶段,而 DataBinder 尤其是WebDataBinder
则主要活泼于 Spring MVC 结构中的数据绑定阶段。详细地,关于 HTTP 恳求参数,在数据绑定过程中,RequestParamMethodArgumentResolver
会派遣 WebDataBinder 进行类型转化;关于 HTTP 恳求头中的参数,RequestHeaderMethodArgumentResolver
仍然会派遣 WebDataBinder 完成类型转化;而关于 HTTP 恳求途径中的参数,PathVariableMethodArgumentResolver
仍是派遣 WebDataBinder 来完成类型转化。但关于 HTTP 恳求体中的参数,RequestResponseBodyMethodProcessor
则是经过HttpMessageConverter
来完成数据绑定的,这儿面的确触及转化,但不应该划分到类型转化的领域,这应该是Jackson
组件的事情了。
在 Spring 官方文档 Spring Type Conversion中介绍 Converter 接口是一个 SPI 接口,这就在暗示我们:假如企图自定义 Converter,那么直接将其声明为一个 Bean 即可,这样 Spring 就会主动将其载入。咱们来看一看这儿的载入逻辑。
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
@Bean
@Override
public FormattingConversionService mvcConversionService() {
Format format = this.mvcProperties.getFormat();
WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
.dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
addFormatters(conversionService);
return conversionService;
}
}
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
@Override
public void addFormatters(FormatterRegistry registry) {
ApplicationConversionService.addBeans(registry, this.beanFactory);
}
}
}
紧接着跟进到ApplicationConversionService
中一探究竟。
public class ApplicationConversionService extends FormattingConversionService {
public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
Set<Object> beans = new LinkedHashSet<>();
beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
for (Object bean : beans) {
if (bean instanceof GenericConverter) {
registry.addConverter((GenericConverter) bean);
} else if (bean instanceof Converter) {
registry.addConverter((Converter<?, ?>) bean);
} else if (bean instanceof Formatter) {
registry.addFormatter((Formatter<?>) bean);
} else if (bean instanceof Printer) {
registry.addPrinter((Printer<?>) bean);
} else if (bean instanceof Parser) {
registry.addParser((Parser<?>) bean);
}
}
}
}
至于如何注册自定义的 PropertyEditor,本文就不介绍了,有了全新的 Converter 类型转化体系,干嘛还使用它呦。