SpringBoot自定义类型转换
前言
ConversionService 是 Spring 中用于类型转换的服务接口,也是转换系统的入口点。
转换器
Converter<S,T>
- 缺少 SourceType 和 TargetType 前置判断
- 仅能转换单一的 SourceType 和 TargetType
ConverterFactory<S, R>
- 一对多的转换,将S类型转换为R的子类
GenericConverter
- 复合类型或通用类型的实现方式
- 多对多的转换
ConditionalConverter
- 在 GenericConverter 基础上加上条件
ConditionalGenericConverter
- 看代码就能猜到是结合了 GenericConverter 和 ConditionalGenericConverter 的功能
1 | public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {} |
统一类型转换服务
GenericConversionService
- 通用 ConversionService 模板实现
- 不内置转换器实现
DefaultConversionService
- 基础 ConversionService 实现
- 内置常用转换器实现
FormattingConversionService
- 通用 Formatter + GenericConversionService 实现
- 不内置转换器和 Formatter 实现
DefaultFormattingConversionService
- DefaultConversionService + 格式化实现
基本用法
1 |
|
List<Integer> 转换 Integer数组,底层使用的是 org.springframework.core.convert.support.CollectionToArrayConverter
自定义类型转换
Spring 中 conversionService 注册时机
在应用启动的时候会去寻找 conversionService 这个 Bean。
Spring 会在 refresh() 方法的 finishBeanFactoryInitialization(beanFactory) 中进行 getBean 然后 set 到 beanFactory 中。
org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
1 | String CONVERSION_SERVICE_BEAN_NAME = "conversionService"; |
所以在整个 Spring 上下文中 conversionService 是唯一的。
Converter 实现
一对一转换 String 转 LocalDateTime
1 | public class LocalDateTimeConverter implements Converter<String, LocalDateTime> { |
ConverterFactory<S, R> 实现
一对多转换,将S类型转换为R的子类
先定义三个 POJO 类
Foo 和 Bar 都继承 Person
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Person {
private String profession;
}
public class Foo extends Person {
private String name;
private Integer age;
}
public class Bar extends Person {
private String name;
private Integer age;
}转换实现
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
29public class PersonConverter implements ConverterFactory<String, Person> {
public <T extends Person> Converter<String, T> getConverter( Class<T> targetType) {
return new StringToPerson<>(targetType);
}
private static final class StringToPerson<T extends Person> implements Converter<String, T> {
private final Class<T> targetType;
public StringToPerson(Class<T> targetType) {
this.targetType = targetType;
}
public T convert(String source) {
String[] split = source.split("-");
if (Foo.class == targetType) {
return (T) new Foo(split[0], Integer.valueOf(split[1]));
} else if (Bar.class == targetType) {
return (T) new Bar(split[0], Integer.valueOf(split[1]));
}
return null;
}
}
}注册到 Spring 中
1
2
3
4
5
6
7
8
9
public class MvcConfig implements WebMvcConfigurer {
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new LocalDateTimeConverter());
registry.addConverterFactory(new PersonConverter());
}
}FormatterRegistry 实际也是继承了 ConverterRegistry,ConversionService 是使用 ConverterRegistry 来注册转换器的。
Converter 是通用元件,可以在应用程序的任意层使用。
Formatter 是专用元件,专门为Web层设计。
Spring 中 conversionService 调用时机
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
1 | protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) |
doCreateBean 中有个 createBeanInstance 方法,点击查看
1 | protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { |
重点在 instantiateBean 中在点进去查看
1 | protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) { |
进入第 13 行的 initBeanWrapper(bw); 方法查看
1 | protected void initBeanWrapper(BeanWrapper bw) { |
再次看到了熟悉的 getConversionService() 设置到 BeanWrapper 中
进入 registerCustomEditors(bw);
1 | protected void registerCustomEditors(PropertyEditorRegistry registry) { |
第 8 行 会把我们自定义的转换器注册进去。
测试
1 |
|
如果使用的是 Post 方式提交 还需要 jackson 的配置
spring 默认使用的序列化器是 jackson
1 |
|
只配置了 String 转 LocalDateTime
这样就相当于是一个全局配置 不用在每个属性上加 @JsonFormat 注解了。
总结
本篇讲述了 Spring 中 ConversinService 作为集中类型转换器的使用与配置。并列举了如何自定义转换器(给出了2个 Demo),更多写法可以参考 DefaultConversionService 中的默认实现。
在结尾的时候讲述了 jackson 转LocalDateTime 的全局化配置。
看多过太多项目中在时间属性上加 @JsonFormat 注解,和还在使用 Date 作为时间类型了(JDK8 开始支持 LocalDateTime 和 LocalDate)
欢迎纠错与讨论,希望本篇对您有所帮助。