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。



