BeanWrapper
BeanWrapper
BeanWrapper是什么
Spring底层操作Java Bean的核心接口。
通常不直接使用该接口,而是通过BeanFactory
或DataBinder
。
提供分析和操作标准Java Bean的操作: 获取和设置属性值(单个或批量)、获取属性描述以及查询属性的可读性/可写性的能力。
此接口还支持嵌套属性,允许将子属性上的属性设置为无限深度。
BeanWrapper
的extractOldValueForEditor
默认值是false
,可以避免调用 getter
方法。将此选项设置为true
,可以向自定义编辑器暴露当前属性值。
可以看出BeanWrapper
是操作Java Bean 的强大利器。
类结构
BeanWrapper 继承自TypeConverter
,PropertyEditorRegistry
,PropertyAccessor
, ConfigurablePropertyAccessor
接口。从名称可以看出具备了类型转换,属性编辑器注册,属性访问及配置的功能。
使用方式
接下来看看如何使用BeanWrapper来操作我们的Java Bean。
Spring给我们提供了一个实现类BeanWrapperImpl
,我们就用这个类来展示。
获取属性
Bean对象:
1 | public class Student { |
定义了3个属性,看一下使用方法:
1 | Student student = new Student(); |
结果如下:
1 | 展示bean 的属性 |
可以看出将有get
方法的属性已经打印出来了。同时可以看到多打印了一个class
属性,但是我们的类里面没有定义这个属性,Object
类中有getClass
的方法。我们大胆猜测Spring会遵循Java Bean的设计原则,通过get
方法来获取属性。
现在将age
改成age1
,getAge
方法不变,看一下结果。
1 | 展示bean 的属性 |
打印出来的属性名一样。现在交换一下,将getAge
改成getAge1
,属性age1
改成age
。
1 | 展示bean 的属性 |
可以看到获取到的属性已经变成了age1
。这充分验证了我们的猜想。
我们可以看一下Spring的代码,里面使用了java.beans
包下Introspector
类来获取Bean的信息。
嵌套属性
上面的结果中,我们并没有获取到ClassRoom的属性。BeanWrapper并不支持这种操作,我们可以扩展一下,比如判断属性,如果是自定义的类型,那么就再调用一次BeanWrapper的方法。这有个前提是这个属性不为null
。
1 | Student student = new Student(); |
先上结果:
1 | 展示bean 的属性 |
ClassRoom类只有一个name 属性,这里看到也打印出来了。证明思路是对的,只是这个结构还需要组织一下,现在是扁平的。
下面看一下PowerfulBeanWrapper
的实现方式
1 | public class PowerfulBeanWrapper extends BeanWrapperImpl { |
直接继承自BeanWrapperImpl
类,覆盖了getPropertyDescriptors
方法。遍历属性值,如果不为空并且不是Class,则再获取一次这个属性的属性。这里只获取了2层属性,可以在获取嵌套属性时换成我们的PowerfulBeanWrapper
类既可支持无限层。
获取属性值
可以使用BeanWrapper的getPropertyValue
方法来获取属性值。上面的代码中已经展示过了。
支持获取嵌套属性:
1 | Student student = new Student(); |
结果:
1 | null |
可以看出来还是很方便的。
注: 当嵌套对象为空时,默认获取嵌套对象的属性会抛出异常。 这时可以加一个设置:
1 | wrapper.setAutoGrowNestedPaths(true); |
该属性的意义是自动扩展嵌套属性,按照默认值来初始化属性。此处就会将classRoom
初始化,并且里面的属性为空。
1 | 嵌套对象为空时:null |
设置属性值
可以通过setPropertyValue
方法来设置属性值。同上,当嵌套对象为空时,不能设置嵌套对象的属性,设置wrapper.setAutoGrowNestedPaths(true)
即可。
注意以下代码:
1 | private String age; |
在这里设置属性值的时候是整数型,但是age
声明的时候是String。BeanWrapper是如何正确的赋值的呢?
BeanWrapperImpl
内部会委托给TypeConverterDelegate
类,先查找自定义PropertyEditor
, 如果没有找到的话,则查找ConversionService
,没有的话查找默认的PropertyEditor
,再没有的话使用内部定义好的转换策略(按类型去判断,然后去转换)。
PropertyEditor
PropertyEditor
属于Java Bean规范里面的类,可以给GUI程序设置对象属性值提供方便,所以接口里有一些和GUI相关的方法,显然目前已经过时了。同时,官方文档上解释,它是线程不安全的。必须得有一个默认构造函数。可以想象一下,在界面上填入一个值,这个值一般来说都是String
类型的,填入之后这个值能自动设置到对应的对象中( 这里纯粹是我意淫的,对awt
并不是很熟,不知道是不是这样)。了解安卓编程的朋友可能知道,我们要取界面上填的值,通常要拿到界面元素,然后再拿到值,然后再设置到对象中去。当界面上有很多个输入控件时,这样繁琐的操作,简直要人命。所以安卓后来出了数据绑定。这里有一篇文章讲得很好。
BeanWrapperImpl内置了一些 PropertyEditor
。
1 | private void createDefaultEditors() { |
这里没有注册String
, 所以走的是内置方案,直接调用toString
方法
1 | if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) { |
自定义PropertyEditor
当Spring提供的PropertyEditor
无法满足我们的需求时,我们可以自定义PropertyEditor
。
一般不直接实现接口,而是继承PropertyEditorSupport
类。Spring中大多数场景都是将传入的字符串转换成对应的属性值,需要重写setAsText
方法。
1 | /** |
上面的代码中,将字符串进行分隔,第一个值作为ClassRoom
的name
值,第二个值作为size
。如何使用这个PropertyEditor
?
先注册这个类,再设置Student
的classRoom
属性:
1 | wrapper = new BeanWrapperImpl(student); |
这样就给Student
类的classRoom
属性进行了初始化。
ConversionService
ConversionService
是Spring提供的一套通用的类型转换机制的入口,相比PropertyEditor
来说:
- 支持的类型转换范围更广。
- 支持从父类型转换为子类型,即多态。
- 省去了Java GUI相关的概念。
- 线程安全。
方法
boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType)
:如果可以将sourceType
对象转换为targetType
,则返回true
。如果此方法返回
true
,则意味着convert(Object, Class)
方法能够将sourceType
实例转换为targetType
。关于集合、数组和Map类型需要特别注意:对于集合、数组和Map类型之间的转换,此方法将返回true,即使在底层元素不可转换的情况下,转换过程仍然可能生成一个
ConversionException
。在处理集合和映射时,调用者需要处理这种特殊情况。boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType)
:如果可以将sourceType
对象转换为targetType
,则返回true
。TypeDescriptor
提供关于将要发生转换的源和目标位置的附加上下文信息,通常是对象字段或属性的位置。如果此方法返回
true
,则意味着convert(Object、TypeDescriptor、TypeDescriptor)
能够将sourceType
实例转换为targetType
。关于集合、数组和Map类型需要特别注意:对于集合、数组和Map类型之间的转换,此方法将返回true,即使在底层元素不可转换的情况下,转换过程仍然可能生成一个
ConversionException
。在处理集合和映射时,调用者需要处理这种特殊情况。<T> T convert(@Nullable Object source, Class<T> targetType)
:将给定的对象转换为指定的targetType
类型对象。Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType)
:将给定的对象转换为指定的targetType
类型对象。TypeDescriptor
提供关于将要发生转换的源和目标位置的附加上下文信息,通常是对象字段或属性位置。
Converter
Converter
是具体的某类转换器接口,负责将某个类型的对象转成另外一个类型的对象。并且是一个函数式接口。
就提供了一个转换方法:
T convert(S source)
:转换对象类型。
ConverterFactory
生产一种Converter
,这种Converter
可以将对象从S
转换为R
的子类型。也就是说支持多态功能。
实现类还可以实现ConditionalConverter
接口。
<T extends R> Converter<S, T> getConverter(Class<T> targetType)
:根据目标类型T
获取Converter
,该Converter
将源类型S
转换成R
的子类型T
。
ConditionalConverter
该接口可以根据源和目标TypeDescriptor
的属性选择性地执行Converter
、GenericConverter
或ConverterFactory
。
通常用于根据字段或类特征(如注解或方法)的存在选择性地匹配自定义转换逻辑。例如,当从String
字段转换为Date
字段时,如果目标字段还有@DateTimeFormat
注解,则实现类matches
方法可能返回true
,也就是说如果目标字段上没有@DateTimeFormat
注解,那么可能不会应用该转换,该接口可以控制需不需要转换。
另外一个例子,当从字符串字段转换为Account
字段时,如果目标Account
类定义了公共静态findAccount(String)
方法,则实现类matches
方法可能返回true
。
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType)
:是否需要转换。
GenericConverter
这是最灵活的转换器SPI接口,也是最复杂的。它的灵活性在于GenericConverter
可以支持多个源/目标类型对之间的转换(参见getConvertibleTypes()
方法)。此外,GenericConverter
实现类在类型转换过程中可以访问源/目标字段上下文,允许解析源和目标字段元数据,如注解和泛型信息,这些信息可用于复杂转换逻辑。
当比较简单的Converter
或ConverterFactory
接口够用时,通常不应使用此接口。
实现类还可以实现ConditionalConverter
接口。
- Set
getConvertibleTypes():返回此 Converter
可以在源类型和目标类型之间转换的类型。
Set中每条都是一个可转换的源到目标类型对。ConvertiblePair
保存源类型与目标类型的映射关系。
对于ConditionalConverter
,此方法可能返回null
,意味着该GenericConverter
适用于所有源与目标类型对。未实现ConditionalConverter
接口的实现类,此方法不能返回null
。
ConverterRegistry
用来注册Converter
。
ConversionService
组件结构
ConversionService
提供转换功能的统一入口,ConverterRegistry
提供Converter
注册功能,将Converter
集中起来,转换时从中查出对应的Converter
。Converter
负责具体的转换过程。ConfigurableConversionService
继承自ConversionService
和ConverterRegistry
,集成转换和注册功能。
下面看一下具体的实现类。
GenericConversionService
类
基础转换服务实现,适用于大部分情况。直接实现ConfigurableConversionService
接口,实现了注册与转换功能。在注册Converter
、ConverterFactory
时,会将其转换成GenericConverter
。
DefaultConversionService
类
继承自GenericConversionService
,配置了适合大多数环境的Converter
。
该类使用时可以直接实例化,暴露出静态方法addDefaultConverters(ConverterRegistry)
,用于对某个ConverterRegistry
实例进行特殊处理,也就是说当某个ConverterRegistry
需要增加一个默认的Converter
时,可以调用这个方法。
这里我们可以直接使用DefaultConversionService
类,Spring已经配置了一些Converter
。
1 | student = new Student(); |
Spring 提供的Converter
在Spring 的org.springframework.core.convert.support
包中内置了一些转换器,提供数组、集合、字符串、数字、枚举、对象、Map、Boolean等之间的转换功能。
Array | Collection | Stream | ByteBuffer | String(Character) | Number(Integer) | Object | Enum | Map | Boolean | Charset | Currency | Locale | Properties | TimeZone | UUID | Calendar | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Array | ArrayToArrayConverter |
ArrayToCollectionConverter |
StreamConverter |
ByteBufferConverter |
ArrayToStringConverter |
ArrayToObjectConverter |
|||||||||||
Collection | CollectionToArrayConverter |
CollectionToCollectionConverter |
StreamConverter |
CollectionToStringConverter |
CollectionToObjectConverter |
||||||||||||
Stream | StreamConverter |
StreamConverter |
|||||||||||||||
String(Character) | StringToArrayConverter |
StringToCollectionConverter |
StringToCharacterConverter |
StringToNumberConverterFactory |
StringToEnumConverterFactory |
StringToBooleanConverter |
StringToCharsetConverter |
StringToCurrencyConverter |
StringToLocaleConverter |
StringToPropertiesConverter |
StringToTimeZoneConverter |
StringToUUIDConverter |
|||||
ByteBuffer | ByteBufferConverter |
ByteBufferConverter |
|||||||||||||||
Number(Integer) | NumberToCharacterConverter |
NumberToNumberConverterFactory |
IntegerToEnumConverterFactory |
||||||||||||||
Object | ObjectToArrayConverter |
ObjectToCollectionConverter |
ByteBufferConverter |
ObjectToStringConverter |
ObjectToObjectConverter ,IdToEntityConverter |
||||||||||||
Enum | EnumToStringConverter |
EnumToIntegerConverter |
|||||||||||||||
Map | MapToMapConverter |
||||||||||||||||
Boolean | |||||||||||||||||
Properties | PropertiesToStringConverter |
||||||||||||||||
ZoneId | ZoneIdToTimeZoneConverter |
||||||||||||||||
ZonedDateTime | ZonedDateTimeToCalendarConverter |
StringToBooleanConverter
将String
转换成Boolean
。true
、on、yes、1
转换成Boolean.TRUE
。false、off
、no 、
0 转换成
Boolean.FALSE`。
1 | wrapper.setPropertyValue("good", "1"); |
自定义Converter
当Spring提供的转换器无法满足我们需要时,我们可以自定义转换逻辑。
上面提到的Converter
、GenericConverter
和ConverterFactory
三个接口都可以用来实现转换逻辑。该如何选择?
Converter
:单值。从S
->T
。一对一转换。ConverterFactory
:从S
->T extends R
。一对多转换。GenericConverter
:功能最复杂。实现多个类型对的转换。多对多转换。
在自定义PropertyEditor
示例中,我们实现了从String
转换到ClassRoom
的功能。在这里通过Converter
来实现此功能。
StringToClassRoomConverter实现
String转换到
ClassRoom`的功能。1
2
3
4
5
6
7
8
9
10public class StringToClassRoomConverter implements Converter<String, ClassRoom> {
public ClassRoom convert(String source) {
String[] strings = Optional.ofNullable(source).orElseGet(String::new).split(",");
ClassRoom classRoom = new ClassRoom();
classRoom.setName(strings[0]);
classRoom.setSize(Integer.parseInt(strings[1]));
return classRoom;
}
}将此
StringToClassRoomConverter
注册到我们使用的ConversionService
中。1
2
3DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToClassRoomConverter());
wrapper.setConversionService(conversionService);BeanWrapper
使用此ConversionService
设置Bean属性。这里不一定用BeanWrapper
来操作,可以直接调用ConversionService
来转换。1
2
3
4
5
6
7wrapper.setPropertyValue("classRoom", "room4,4");
System.out.println("自定义Converter, 设置嵌套对象的属性 name:" + wrapper.getPropertyValue("classRoom.name"));
System.out.println("自定义Converter, 设置嵌套对象的属性 size:" + wrapper.getPropertyValue("classRoom.size"));
//---------
//自定义Converter, 设置嵌套对象的属性 name:room4
//自定义Converter, 设置嵌套对象的属性 size:4从输出结果来看,已经成功转换成功。
当我们想从一个类型转换成某些类型时,可以实现ConverterFactory
接口,因为我们也不知道总共有哪些类型,不可能每个类型都写一个ConverterFactory
。比如说从String
转换成枚举类型,前端传枚举类型的字面值,转换成具体的枚举类型。Spring 内置了StringToEnumConverterFactory
来实现此功能。直接调用Enum的静态方法Enum.valueOf(this.enumType, source.trim())
来实现转换。同理还有IntegerToEnumConverterFactory
通过枚举的序号来转换。
当我们遇到容器型的转换需求时,因为容器内部保存的类型可能是多种多样的,比如说List里面既有String,也有int,我们要统一转成Long型。
1 | DefaultConversionService conversionService = new DefaultConversionService(); |
如果有多个Converter
可以处理同一个转换需求,那么则看注意的先后顺序了,会取第一个符合条件的转换器。这里可以优化一下。
Formatter
在前后端交互时,通常会遇到日期格式这样的问题,Converter
虽然说也能解决这个问题,在转换时获取到正确的格式然后进行转换。但是这样无法灵活控制我们的格式,我们得把所有格式都写在我们的Converter
里,换一种格式,又得改一次这个类。这时候Formatter
接口出现了。
Formatter
接口位于context
包中。
先看一下Formatter
类,从Parser
和Printer
接口继承而来,实现了String
<-> Object
转换的功能。
FormatterRegistry
继承自ConverterRegistry
接口,用来注册Formatter
。
void addFormatter(Formatter<?> formatter)
:向特定类型的字段添加Formatter
。字段类型由Formatter
的泛型提供。void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter)
:向给定类型的字段添加Formatter
。在打印时,如果声明了
Formatter
的类型T
,并且不能将fieldType
赋值给T
,则在打印字段值的任务委托给Formatter
之前,将尝试强制转换为T
。在解析时,如果Formatter
返回的已解析对象不能分配给运行时字段类型,则在返回已解析字段值之前,将尝试强制转换为fieldType
。例如,DateFormatter
声明的泛型为Date
,如果这里的fieldType
为DateTime
(假设存在),如果DateTime
可以用Date
变量接收,则意味着可以分配给Date
,也就不需要转换。否则则需要转换为Date
类型。具体能否分配,可以查看该方法:org.springframework.core.convert.TypeDescriptor#isAssignableTo
。void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser)
:添加Printer
/Parser
对来格式化特定类型的字段,formatter 将委托给指定的Printer
进行打印,并委托指定的Parser
进行解析。在打印时,如果声明了
Printer
的类型T
,并且fieldType
不能赋值给T
,则在委托Printer
打印字段值之前,将尝试转换化类型T
。在解析时,如果Parser
返回的对象不能分配给fieldType
,则在返回解析后的字段值之前,将尝试转换为fieldType
。这个方法与上一个方法的区别就是将Formatter
拆开。void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory)
:为格式化注解标注的字段添加Formatter
。此方法与上述方法的区别是不再以类型作为转换的依据了,而是根据注解来转换。比如某个字段上使用了DateTimeFormat
注解,那会调用对应的Formatter
。
FormattingConversionService
FormattingConversionService
类是Spring提供的支持Formatter
接口的实现类,继承自GenericConversionService
类,实现了FormatterRegistry
和EmbeddedValueResolverAware
接口,在GenericConversionService
类的基础上增加了注册Formatter
的功能。EmbeddedValueResolverAware
接口用来解决字符串占位符、国际化等问题。注册Formatter
时,会将Formatter
转换成GenericConverter
,调用此GenericConverter
的convert
方法时,将会调用Formatter
的print
或parse
方法,由此可以看出我们需要格式化时,还是调用FormattingConversionService
的convert
方法即可。
DefaultFormattingConversionService
Spring 内部提供了一些Formatter
,会通过DefaultFormattingConversionService
注册,我们无不特殊要求,可以直接使用此类,再在此基础上注册我们自定义的Formatter
。
Spring提供的Formatter
Formatter 或FormatterRegistrar |
DefaultFormattingConversionService 是否注册 |
说明 |
---|---|---|
NumberFormatAnnotationFormatterFactory |
Y | 用于支持@NumberFormat 注解 |
CurrencyUnitFormatter |
JSR-354相关的jar包出现在classpath时注册 | javax.money.CurrencyUnit |
MonetaryAmountFormatter |
JSR-354相关的jar包出现在classpath时注册 | javax.money.MonetaryAmount |
Jsr354NumberFormatAnnotationFormatterFactory |
JSR-354相关的jar包出现在classpath时注册 | @NumberFormat 注解 |
DateTimeFormatterRegistrar |
用来注册JSR-310 新版日期和时间相关 |
|
JodaTimeFormatterRegistrar |
如果使用了Joda 包,则会注册相关的`` |
|
DateFormatterRegistrar |
注册@DateTimeFormat 注解的AnnotationFormatterFactory 用于Date ,Calendar ,Long 之间格式化以及Date ,Calendar ,Long 之间的转换的Converter 。默认不会注册用于直接转换的DateFormatter ,不需要@DateTimeFormat 注解,我们可以手动注册。 |
使用示例
- 以日期为例,如果不用注解的话,我们需要手动注册一下
Formatter
。
1 | DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); |
DateFormatter
还支持指定格式。可以通过构造函数传入。
1 | conversionService.addFormatter(new DateFormatter("yyyy-MM-dd")); |
还是以日期为例,使用Spring提供的
@DateTimeFormat
注解。先创建一个类,里面有个日期字段使用
@DateTimeFormat
注解。1
2
3class Question {
private Date createTime;指定格式为
yyyy-MM-dd
,这里我们借助BeanWrapper
来触发格式化的动作。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
Question question = new Question();
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(question);
beanWrapper.setConversionService(conversionService);
beanWrapper.setPropertyValue("createTime", "2019-09-03");
System.out.println(question.getCreateTime());
//-----------
// Tue Sep 03 00:00:00 CST 2019
// 将时间格式化 字符串
System.out.println("注解格式化日期:" + conversionService.convert(question.getCreateTime(), new TypeDescriptor(question.getClass().getDeclaredField("createTime")), TypeDescriptor.valueOf(String.class)));
//---------
//注解格式化日期:2019-09-03通过打印的信息,可以看到已经成功将字符串
parse
为Date
,并将日期format
为字符串。由于注解在字段上,我们只提供了Date
的值,所以还需要通过TypeDescriptor
将字段的附加信息传递进去,这样才能正确识别到字段上的注解。
自定义Formatter
除了Spring 给我们提供的这些Formatter
之外,我们还可以自定义来实现特殊功能。
比如前台传过来一段字符串,我们根据正则表达式截取部分字符。
定义StringFormat
注解
此注解用来标注该字段需要用我们的自定义逻辑。可以指定具体的正则表达式。
1 |
|
定义StringFormatAnnotationFormatterFactory
此类实现AnnotationFormatterFactory
接口,规定StringFormat
注解支持的字段类型。我们使用正则分隔字符串,那么可能得到多个目标串,所以getFieldTypes
返回List
来接收目标类型。getParser
方法返回我们的自定义StringFormatFormatter
。
1 | /** |
自定义 StringFormatFormatter
该类实现Formatter
。用来负责具体的解析逻辑。该类需要使用到注解中定义的正则表达式,这样我们就可以灵活控制每个字段的转换规则了。
1 | private static class StringFormatFormatter implements Formatter<Collection> { |
使用
将我们的Formatter
注册到FormattingConversionService
,遇到对应的转换,则会调用我们的Formatter
。下面的例子中StringFormatEntity
类使用到了自定义的StringFormat
注解。
1 |
|
从打印出来的结果可以看出来已经正确将字符串转换成了List
。不过List
中都是字符串,我们还可以将字符串转换成数字类型。需要我们来主动转换吗?其实是不需要的,Spring 已经帮我们考虑到了此种情景,可以自动将List
中的元素也转换成对应的类型。还记得CollectionToCollectionConverter
这个转换器吗?不过有个细节要注意:**我们的Formatter
返回的结果不能是ArrayList
, 这样会丢失泛型,不能正确转换,所以我们可以返回ArrayList
的子类List<String> list = new ArrayList<>(){};
, 这样会保留泛型,会调用后续的Converter
**。
Converter
的注册与获取
主要通过ConverterRegistry
和FormatterRegistry
来注册以及移除Converter
。
ConverterRegistry
方法
void addConverter(Converter<?, ?> converter)
:注册简单的Converter
,转换类型从Converter
的泛型中获取。<S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter)
: 注册普通转换器,并且明确指定可转换类型。可以针对多个不同转换类型的情况重用
Converter
,而不必为每个对创建Converter
类。指定的源类型是Converter
定义的类型的子类型,目标类型是Converter
定义的类型的父类型。为什么要如此定义?拿Spring提供的ObjectToStringConverter
为例,该Converter
定义的转换类型为Object
->String
,调用Object.toString()
方法,只要是Object
的子类,都可以调用此方法转换成String
,因为toString()
是共有的方法。同理,目标类型指定的类型需要是我定义的父类型,这样转换出来的一定是需要的类型。void addConverter(GenericConverter converter)
:注册GenericConverter
。void addConverterFactory(ConverterFactory<?, ?> factory)
:注册ConverterFactory
。void removeConvertible(Class<?> sourceType, Class<?> targetType)
:移除sourceType
到targetType
的转换功能。
GenericConversionService
GenericConversionService
类实现了ConverterRegistry
接口。现在看一下具体的注册过程。
void addConverter(Converter<?, ?> converter)
:因为没有指定转换类型,所以只能从
Converter
的泛型中获取转换类型,如果获取不到,则会抛出异常。获取到之后,则会创建ConverterAdapter
实例,通过void addConverter(GenericConverter converter)
方法进行注册。1
2
3
4
5
6
7
8
9ResolvableType[] typeInfo = getRequiredTypeInfo(converter.getClass(), Converter.class);
//如果是代理对象,还需要从代理类中获取
if (typeInfo == null && converter instanceof DecoratingProxy) {
typeInfo = getRequiredTypeInfo(((DecoratingProxy) converter).getDecoratedClass(), Converter.class);
}
if (typeInfo == null) {
throw new IllegalArgumentException("Unable to determine source type <S> and target type <T> for your " + "Converter [" + converter.getClass().getName() + "]; does the class parameterize those types?");
}
addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1]));<S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter)
:由于指定了转换类型,直接注册就完事了。
1
addConverter(new ConverterAdapter(converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)));
void addConverterFactory(ConverterFactory<?, ?> factory)
:也是先从泛型中推断出转换的类型,然后创建
ConverterFactoryAdapter
实例进行注册。1
2
3
4
5
6
7
8ResolvableType[] typeInfo = getRequiredTypeInfo(factory.getClass(), ConverterFactory.class);
if (typeInfo == null && factory instanceof DecoratingProxy) {
typeInfo = getRequiredTypeInfo(((DecoratingProxy) factory).getDecoratedClass(), ConverterFactory.class);
}
if (typeInfo == null) {
throw new IllegalArgumentException("Unable to determine source type <S> and target type <T> for your " + "ConverterFactory [" + factory.getClass().getName() + "]; does the class parameterize those types?");
}
addConverter(new ConverterFactoryAdapter(factory, new ConvertiblePair(typeInfo[0].toClass(), typeInfo[1].toClass())));void addConverter(GenericConverter converter)
:上面几种注册方式最终都会调用此方法,也就是说会将
Converter
、ConverterFactory
转换成GenericConverter
。这里使用到了适配器模式
。1
2
3
4//添加到内部容器中去
this.converters.add(converter);
//使缓存失效
invalidateCache();
ConverterAdapter
适配器
实现了ConditionalGenericConverter
接口,将Converter
转换成GenericConverter
。在matches
方法去判断转换类型是否匹配。转换时直接调用内部转换器的转换方法。
ConverterFactoryAdapter
适配器
实现了ConditionalGenericConverter
接口,将ConverterFactoryAdapter
转换成GenericConverter
。在matches
方法去判断转换类型是否匹配。转换时直接调用内部ConverterFactory
获取的转换器的转换方法。
ConvertiblePair
该类保存了转换的源类型和目标类型,并重写了equals
和hashCode
方法用于比较。GenericConverter
返回ConvertiblePair
集合表示所支持的转换类型。
Converters
此类用来管理所有注册的Converter
。提供添加和删除的功能。添加时获取到此Converter
支持的类型,如果为空并且是ConditionalConverter
,则代表它支持所有类型。得到支持的类型后,遍历每个类型,获取到已经注册的ConvertersForPair
,该类维护转换类型到Converter
之间的关系,而且是一对多的关系,也就是说同一种转换类型,会存在多个Converter
。拿到ConvertersForPair
后,将该Converter
添加进去,后添加的会在前面,获取时符合条件时会优先返回。
1 | public void add(GenericConverter converter) { |
FormatterRegistry
注册 Formatter
继承自ConverterRegistry
接口,增加了注册Formmater
的方法。
FormattingConversionService
该类继承自GenericConversionService
类,并实现了FormatterRegistry
接口。在添加Formatter
时会将其转换为PrinterConverter
,ParserConverter
。在添加AnnotationFormatterFactory
转换为AnnotationPrinterConverter
和AnnotationParserConverter
。可以想象到这四个类也是实现了Converter
,最终通过convert
方法来调用parse
和print
方法。
获取Converter
在GenericConversionService
的转换过程中,来了一个转换类型,需要获取到对应的Converter
。在Converters
的find
方法中先拿到源类型和目标类型继承的所有类型(包括接口),比如说源类型是String
,那么获取到的就是String
、Serializable
、Comparable
、CharSequence
和Object
,如果是枚举还将获取到Enum
。找到之后一一进行组合去获取Converter
,比如目标类型是Integer
,则第一次组合就是String
->Integer
,如果找到了支持String
->Integer
的Converter
,则会返回这个。这么做的目的是支持一个Converter
可以转换多个类型,比如String
-> Enum
,通过字面量转换成枚举,如果没有这个机制,那么我们就得为每个枚举都定义一个Converter
,但是有了这个机制,我们就可以支持所有的枚举类型。其实就是通过这个机制来支持ConverterFactory
。这个机制可以保证子类可以通过父类转换器进行转换(这种转换方式需要注意父类无法感知子类的特殊属性),但不能保证父类可以通过子类转换器,如果可以保证Converter
能正确转换,则可以通过<S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter)
方法显式进行注册。比如我们只有String
->Integer
的Converter
,但是我们需要将String
转换为Number
,则可以通过这个方法注册addConverter(String.class, Number.class, StringToIntegerConverter)
。
1 | DefaultConversionService conversionService = new DefaultConversionService(); |
下面是查找的大致过程:
DirectFieldAccessor
通过反射直接访问Bean实例的字段。可以直接绑定到字段,而不需要通过JavaBean set方法。
从Spring 4.2开始,绝大多数BeanWrapper
特性已经被合并到AbstractPropertyAccessor
中,这意味着这个类也支持属性遍历以及集合和Map 访问。
DirectFieldAccessor
的extractOldValueForEditor
属性默认为true
,因为在读取字段的时候是直接通过反射去拿到的字段值,不需要调用getter
方法。
PropertyAccessorFactory
可以通过此类来获取BeanWrapper
和DirectFieldAccessor
。