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接口。
- SetgetConvertibleTypes():返回此 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
 10- public 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
 3- DefaultConversionService conversionService = new DefaultConversionService(); 
 conversionService.addConverter(new StringToClassRoomConverter());
 wrapper.setConversionService(conversionService);
- BeanWrapper使用此- ConversionService设置Bean属性。这里不一定用- BeanWrapper来操作,可以直接调用- ConversionService来转换。- 1 
 2
 3
 4
 5
 6
 7- wrapper.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
 3- class Question { 
 
 private Date createTime;- 指定格式为 - yyyy-MM-dd,这里我们借助- BeanWrapper来触发格式化的动作。- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16- DefaultFormattingConversionService 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
 9- ResolvableType[] 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
 8- ResolvableType[] 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。




