或许大家对 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 开端,类型转化领域迎来了三位新的王者,分别是:ConverterConverterFactoryGenericConverter,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 中类型转化体系的应用场景与派遣模型。

宝,你了解 Spring 中的类型转换体系吗

BeanWrapperDataBinder都从属于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 类型转化体系,干嘛还使用它呦。