文章目录
- 一、概述
- 1、Spring 类型转换的实现
- 2、使用场景
- 3、源码分析
- 二、基于 JavaBeans 接口的类型转换
- 1、代码实例
- 2、Spring 內建 PropertyEditor 扩展
- ByteArrayPropertyEditor
- 3、自定义 PropertyEditor 扩展整合到springframework
- 代码实例
- Spring PropertyEditor 的设计缺陷
- 三、Spring 3 通用类型转换接口
- 1、初识Converter、GenericConverter接口
- 2、Spring 內建类型转换器
- 3、Converter 接口的局限性及解决方案
- 局限一:缺少 Source Type 和 Target Type 前置判断
- 局限二:仅能转换单一的 Source Type 和 Target Type
- 4、GenericConverter 接口详解
- GenericConverter接口的局限性
- GenericConverter 优化接口 - ConditionalGenericConverter接口
- 案例分析
- 5、扩展 Spring 类型转换器
- 代码实例
- 6、内置统一类型转换服务-ConversionService
- tips1
- tips2
- 7、ConversionService 作为依赖
- TypeConverter
- TypeConverterSupport
- TypeConverterDelegate
- 整体流程
- 参考资料
一、概述
本文主要内容是Spring Bean通过xml等配置进行属性映射时,发生的类型转换的基本原理与源码分析。
1、Spring 类型转换的实现
Spring 3.0以前,基于 JavaBeans 接口的类型转换实现,基于 java.beans.PropertyEditor 接口扩展。
Spring 3.0+ 通用类型转换实现。
2、使用场景
场景 | 基于 JavaBeans 接口的类型转换实现 | Spring 3.0+ 通用类型转换实现 |
数据绑定 | YES | YES |
BeanWrapper | YES | YES |
Bean 属性类型装换 | YES | YES |
外部化属性类型转换 | NO | YES |
3、源码分析
我们之前提到过:Spring数据绑定详解,Spring-Data Binding源码分析
数据绑定通过DataBinder进行绑定的,而DataBinder类实现了PropertyEditorRegistry和TypeConverter。
PropertyEditorRegistry接口和PropertyEditor 有着直接的关系:
public interface PropertyEditorRegistry {
void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor);
@Nullable
PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);
}
BeanWrapper接口继承了ConfigurablePropertyAccessor接口,而ConfigurablePropertyAccessor接口提供了对ConversionService(类型转换服务)的操作,同时有个开关,设置是否用PropertyEditor抽取老的值。
上面了解了一些关于类型转换的接口后,我们继续回到DefaultListableBeanFactory的父类AbstractAutowireCapableBeanFactory的doCreateBean方法,同样用到了BeanWrapper,在调用populateBean时,会执行applyPropertyValues方法,这与DataBinder的bind方法中的applyPropertyValues方法的实现基本是一致的。
applyPropertyValues方法中就涉及到了我们本文的重点-Bean属性绑定时发生的类型转换。
二、基于 JavaBeans 接口的类型转换
核心职责:将 String 类型的内容转化为目标类型的对象
扩展原理:
• Spring 框架将文本内容传递到 PropertyEditor 实现的 setAsText(String) 方法
• PropertyEditor#setAsText(String) 方法实现将 String 类型转化为目标类型的对象
• 将目标类型的对象传入 PropertyEditor#setValue(Object) 方法
• PropertyEditor#setValue(Object) 方法实现需要临时存储传入对象
• Spring 框架将通过 PropertyEditor#getValue() 获取类型转换后的对象
1、代码实例
import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.io.StringReader;
import java.util.Map;
import java.util.Properties;
/**
* String -> Properties {@link PropertyEditor}
*/
public class StringToPropertiesPropertyEditor extends PropertyEditorSupport implements PropertyEditor {
// 1. 实现 setAsText(String) 方法
@Override
public void setAsText(String text) throws IllegalArgumentException {
// 2. 将 String 类型转换成 Properties 类型
Properties properties = new Properties();
try {
properties.load(new StringReader(text));
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
// 3. 临时存储 Properties 对象
setValue(properties);
// 接下来要 获取临时 Properties 对象 -通过getValue();
}
@Override
public String getAsText() {
Properties properties = (Properties) getValue();
StringBuilder textBuilder = new StringBuilder();
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
textBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append(System.getProperty("line.separator")); // 换行符
}
return textBuilder.toString();
}
}
// 模拟 Spring Framework 操作
// 有一段文本 name = 张三;
String text = "name = 张三";
PropertyEditor propertyEditor = new StringToPropertiesPropertyEditor();
// 传递 String 类型的内容
propertyEditor.setAsText(text);
System.out.println(propertyEditor.getValue());
System.out.println(propertyEditor.getAsText());
2、Spring 內建 PropertyEditor 扩展
內建扩展(org.springframework.beans.propertyeditors 包下)(仅限于String转换为其他类型)
转换场景 | 实现类 |
String -> Byte 数组 | org.springframework.beans.propertyeditors.ByteArrayPropertyEditor |
String -> Char | org.springframework.beans.propertyeditors.CharacterEditor |
String -> Char 数组 | org.springframework.beans.propertyeditors.CharArrayPropertyEditor |
String -> Charset | org.springframework.beans.propertyeditors.CharsetEditor |
String -> Class | org.springframework.beans.propertyeditors.ClassEditor |
String -> Currency | org.springframework.beans.propertyeditors.CurrencyEditor |
… | … |
ByteArrayPropertyEditor
我们可以看出,基本也都是继承了PropertyEditorSupport ,实现了setAsText和getAsText方法。其他转换器都类似,我们上面的代码实例也是这样实现的。
public class ByteArrayPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(@Nullable String text) {
setValue(text != null ? text.getBytes() : null);
}
@Override
public String getAsText() {
byte[] value = (byte[]) getValue();
return (value != null ? new String(value) : "");
}
}
3、自定义 PropertyEditor 扩展整合到springframework
扩展模式: 扩展 java.beans.PropertyEditorSupport 类
实现 org.springframework.beans.PropertyEditorRegistrar
• 实现 registerCustomEditors(org.springframework.beans.PropertyEditorRegistry) 方法
• 将 PropertyEditorRegistrar 实现注册为 Spring Bean
向 org.springframework.beans.PropertyEditorRegistry 注册自定义 PropertyEditor 实现
• 通用类型实现 registerCustomEditor(Class<?>, PropertyEditor) • Java Bean 属性类型实现:registerCustomEditor(Class<?>, String, PropertyEditor)
代码实例
我们复用上面的StringToPropertiesPropertyEditor。
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.stereotype.Component;
/**
* 自定义 {@link PropertyEditorRegistrar} 实现
*
* @see PropertyEditorRegistrar
*/
// @Component // 3. 将其声明为 Spring Bean
public class CustomizedPropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
// 1. 通用类型转换
// 2. Java Bean 属性类型转换
registry.registerCustomEditor(Properties.class, "context", new StringToPropertiesPropertyEditor());
}
}
<bean id="customEditorConfigurer" class="com.test.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<bean class="com.test.conversion.CustomizedPropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="user" class="com.test.domain.User">
<property name="id" value="1"/>
<property name="name" value="张三"/>
<property name="context"> <!-- Properties 类型 -->
<value>
id = 1
name = zhangsan
</value>
</property>
<property name="contextAsText" ref="context"/> <!-- Properties -> String -->
</bean>
private Properties context;
private String contextAsText;
Spring PropertyEditor 的设计缺陷
Spring3之后弃用了PropertyEditor ,PropertyEditor 的设计缺陷主要有以下三种:
1、违反职责单一原则
• java.beans.PropertyEditor 接口职责太多,除了类型转换,还包括 Java Beans 事件和 Java GUI 交互
2、java.beans.PropertyEditor 实现类型局限
• 来源类型只能为 java.lang.String 类型
3、java.beans.PropertyEditor 实现缺少类型安全
• 除了实现类命名可以表达语义,实现类无法感知目标转换类型
三、Spring 3 通用类型转换接口
1、初识Converter、GenericConverter接口
类型转换接口 - org.springframework.core.convert.converter.Converter<S,T>
• 泛型参数 S:来源类型,参数 T:目标类型
• 核心方法:T convert(S)
通用类型转换接口 - org.springframework.core.convert.converter.GenericConverter
• 核心方法:convert(Object,TypeDescriptor,TypeDescriptor)
• 配对类型:org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
• 类型描述:org.springframework.core.convert.TypeDescriptor
我们看一下Converter接口,泛型S、T分别代表来源和目标类型,这个接口的实现是线程安全的,并且可以共享:
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S source);
}
我们再看一下ConditionalConverter接口,Converter接口尽管强大但是仍有不足,增加了一个有条件的接口:
public interface ConditionalConverter {
// 转换之前,预判输入类型和输出类型
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
TypeDescriptor 就是类型描述,里面包含了许多类型的信息。
我们再来看一下通用类型转换接口-GenericConverter接口,使用ConvertiblePair包装了一个Set,可以支持多种类型:
public interface GenericConverter {
@Nullable
Set<ConvertiblePair> getConvertibleTypes();
@Nullable
Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
/**
* Holder for a source-to-target class pair.
*/
final class ConvertiblePair {
private final Class<?> sourceType;
private final Class<?> targetType;
/**
* Create a new source-to-target pair.
* @param sourceType the source type
* @param targetType the target type
*/
public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
Assert.notNull(sourceType, "Source type must not be null");
Assert.notNull(targetType, "Target type must not be null");
this.sourceType = sourceType;
this.targetType = targetType;
}
public Class<?> getSourceType() {
return this.sourceType;
}
public Class<?> getTargetType() {
return this.targetType;
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (other == null || other.getClass() != ConvertiblePair.class) {
return false;
}
ConvertiblePair otherPair = (ConvertiblePair) other;
return (this.sourceType == otherPair.sourceType && this.targetType == otherPair.targetType);
}
@Override
public int hashCode() {
return (this.sourceType.hashCode() * 31 + this.targetType.hashCode());
}
@Override
public String toString() {
return (this.sourceType.getName() + " -> " + this.targetType.getName());
}
}
}
2、Spring 內建类型转换器
转换场景 | 实现类所在包名(package) |
日期/时间相关 | org.springframework.format.datetime |
Java 8 日期/时间相关 | org.springframework.format.datetime.standard |
通用实现 | org.springframework.core.convert.support |
通过这些源码来看,使用方式与上面的PropertyEditor方式有着较大的差别(篇幅限制,大家自行翻阅源码)。
3、Converter 接口的局限性及解决方案
局限一:缺少 Source Type 和 Target Type 前置判断
Converter 只提供了一个T convert(S source);方法,输入一个参数返回一个参数,没有判断是否支持这个参数类型的转换。
应对:增加 org.springframework.core.convert.converter.ConditionalConverter 实现,同时实现Converter、ConditionalConverter 接口,其中ConditionalConverter 接口的matches方法可以判断是否支持该类型参数的转换。
局限二:仅能转换单一的 Source Type 和 Target Type
Converter 只提供了一个T convert(S source);方法,输入一个参数返回一个参数,对集合类型的参数转换不友好。
应对:使用 org.springframework.core.convert.converter.GenericConverter 代替,GenericConverter 接口我们上面也分析到,用一个内部类ConvertiblePair可以实现集合类型转换。
实际上,Converter和GenericConverter 会相互配合,简单类型的转换一般使用Converter,复合类型的转换一般使用GenericConverter
4、GenericConverter 接口详解
org.springframework.core.convert.converter.GenericConverter接口,是一个通用的类型转换接口,它比Converter适用性更广。
核心要素 | 说明 |
使用场景 | 主要用于“复合”类型转换场景,比如 Collection、Map、数组等 |
转换范围 | Set<ConvertiblePair> getConvertibleTypes() |
配对类型 | org.springframework.core.convert.converter.GenericConverter.ConvertiblePair |
转换方法 | convert(Object,TypeDescriptor,TypeDescriptor) |
类型描述 | org.springframework.core.convert.TypeDescriptor |
GenericConverter接口的局限性
与Converter接口一样,GenericConverter接口也缺少 Source Type 和 Target Type 前置判断。
同时,GenericConverter接口单一类型转换实现复杂,也就是说,GenericConverter比较适合复合类型的转换,Converter接口比较适合单一类型的转换。
GenericConverter 优化接口 - ConditionalGenericConverter接口
ConditionalGenericConverter接口整合了GenericConverter接口和ConditionalConverter接口,解决了GenericConverter接口缺少 Source Type 和 Target Type 前置判断的问题。
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
案例分析
我们分析一下StringToCollectionConverter转换类(其他大量转换类同理):
final class StringToCollectionConverter implements ConditionalGenericConverter {
private final ConversionService conversionService; // 通过ConversionService 来进行处理
public StringToCollectionConverter(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, Collection.class));
}
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
// 通过ConversionService 来进行处理,判断类型是否匹配
return (targetType.getElementTypeDescriptor() == null ||
this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()));
}
@Override
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
String string = (String) source;
String[] fields = StringUtils.commaDelimitedListToStringArray(string);
TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
Collection<Object> target = CollectionFactory.createCollection(targetType.getType(),
(elementDesc != null ? elementDesc.getType() : null), fields.length);
if (elementDesc == null) {
for (String field : fields) {
target.add(field.trim());
}
}
else {
for (String field : fields) {
Object targetElement = this.conversionService.convert(field.trim(), sourceType, elementDesc);
target.add(targetElement);
}
}
return target;
}
}
5、扩展 Spring 类型转换器
实现转换器接口
• org.springframework.core.convert.converter.Converter
• org.springframework.core.convert.converter.ConverterFactory
• org.springframework.core.convert.converter.GenericConverter
注册转换器实现
• 通过 ConversionServiceFactoryBean Spring Bean
• 通过 org.springframework.core.convert.ConversionService API
代码实例
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* {@link Properties} -> {@link String} {@link ConditionalGenericConverter} 实现
*
* @see Properties
* @see ConditionalGenericConverter
*/
public class PropertiesToStringConverter implements ConditionalGenericConverter {
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return Properties.class.equals(sourceType.getObjectType())
&& String.class.equals(targetType.getObjectType());
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Properties.class, String.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Properties properties = (Properties) source;
StringBuilder textBuilder = new StringBuilder();
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
textBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append(System.getProperty("line.separator"));
}
return textBuilder.toString();
}
}
<!-- 声明 ConversionServiceFactoryBean 并且 name 必须为 "conversionService" -->
<bean id="conversionService" class="com.test.ConversionServiceFactoryBean">
<property name="converters">
<bean class="com.test.PropertiesToStringConverter"/>
</property>
</bean>
<!-- java.util.Properties -->
<util:properties id="context">
<prop key="id">1</prop>
<prop key="name">zhangsan</prop>
</util:properties>
<bean id="user" class="com.test.domain.User">
<property name="contextAsText" ref="context"/> <!-- Properties -> String -->
</bean>
6、内置统一类型转换服务-ConversionService
通过类型转换的源码我们看到,类型转换都需要使用org.springframework.core.convert.ConversionService类型转换服务。
实现类型 | 说明 |
GenericConversionService | 通用 ConversionService 模板实现,不内置转化器实现 |
DefaultConversionService | 基础 ConversionService 实现,内置常用转化器实现 |
FormattingConversionService | 通用 Formatter + GenericConversionService 实现,不内置转化器和Formatter 实现 |
DefaultFormattingConversionService | DefaultConversionService + 格式化 实现(如:JSR-354 Money & Currency, JSR-310 Date-Time) |
以上是Spring自带的类型转换服务,Springboot中提供了WebConversionService继承了DefaultFormattingConversionService,在这个基础上做了一些扩展。
tips1
在xml文件中注册ConversionServiceFactoryBean,此类中组合了GenericConversionService(实际上是DefaultConversionService),该类作为Bean定义时,名称一定要是conversionService。
在AbstractApplicationContext#finishBeanFactoryInitialization中,beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
},
此处会以CONVERSION_SERVICE_BEAN_NAME(conversionService)作为bean名称,ConversionService.class作为类型传进去,出于好奇,想对比一下xml文件中定义的conversionService和beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class))获取到的是不是同一个对象,结果发现applicationContext.getBean(“conversionService”)拿到的居然不是ConversionServiceFactoryBean,而是其组合的DefaultConversionService,通过跟踪getBean发现,在AbstractBeanFactory#getObjectForBeanInstance中会判断该对象是否为FactoryBean,如果是,则在FactoryBeanRegistrySupport#doGetObjectFromFactoryBean中通过factory.getObject()返回,返回的正是ConversionServiceFactoryBean中组合的DefaultConversionService。
因此beanFactory.setConversionService中传入的就是ConversionServiceFactoryBean中组合的DefaultConversionService,在doCreatBean中,会去获取BeanWrapper实例(BeanWrapperImpl),实际上最终是通过
protected void initBeanWrapper(BeanWrapper bw) {
bw.setConversionService(getConversionService());
registerCustomEditors(bw);
}
设置conversionService对象。
综上所述,conversionService实际是ConversionServiceFactoryBean中组合的GenericConversionService贯穿了整个上下文。
tips2
那么GenericConversionService是在什么时候被实例化的呢?
不难发现,ConversionServiceFactoryBean实现了InitializingBean接口,在回调方法afterPropertiesSet中实例化:
this.conversionService = createConversionService();
protected GenericConversionService createConversionService() {
return new DefaultConversionService();
}
构造GenericConversionService的过程中,注册了一些诸如:ByteBufferConverter、StringToTimeZoneConverter、ZoneIdToTimeZoneConverterZonedDateTimeToCalendarConverter、ObjectToObjectConverter、IdToEntityConverter、FallbackObjectToStringConverter、ObjectToOptionalConverter的转换器,然后通过ConversionServiceFactory.registerConverters(this.converters, this.conversionService),将自定义的转换器注册到conversionService中。
7、ConversionService 作为依赖
类型转换器底层接口 - org.springframework.beans.TypeConverter
• 起始版本:Spring 2.0
• 核心方法 - convertIfNecessary 重载方法
• 抽象实现 - org.springframework.beans.TypeConverterSupport
• 简单实现 - org.springframework.beans.SimpleTypeConverter
类型转换器底层抽象实现 - org.springframework.beans.TypeConverterSupport
• 实现接口 - org.springframework.beans.TypeConverter
• 扩展实现 - org.springframework.beans.PropertyEditorRegistrySupport
• 委派实现 - org.springframework.beans.TypeConverterDelegate
类型转换器底层委派实现 - org.springframework.beans.TypeConverterDelegate
• 构造来源 - org.springframework.beans.AbstractNestablePropertyAccessor 实现:org.springframework.beans.BeanWrapperImpl
• 依赖 - java.beans.PropertyEditor 实现:默认內建实现 - PropertyEditorRegistrySupport#registerDefaultEditors
• 可选依赖 - org.springframework.core.convert.ConversionService 实现
TypeConverter
TypeConverter接口在Spring2.0时就已经存在了。
TypeConverter接口有三个重载方法:convertIfNecessary,顾名思义,就是如果能转的时候就会转换。
TypeConverterSupport
TypeConverterSupport抽象类继承了PropertyEditorRegistrySupport,实现了TypeConverter。
// org.springframework.beans.TypeConverterSupport#convertIfNecessary(java.lang.Object, java.lang.Class<T>, org.springframework.core.convert.TypeDescriptor)
@Nullable
@Override
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
try {
return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);
}
catch (ConverterNotFoundException | IllegalStateException ex) {
throw new ConversionNotSupportedException(value, requiredType, ex);
}
catch (ConversionException | IllegalArgumentException ex) {
throw new TypeMismatchException(value, requiredType, ex);
}
}
通过以上源码可以看出,它会将类型转换委派给TypeConverterDelegate,调用其convertIfNecessary方法。
TypeConverterDelegate
TypeConverterDelegate的核心方法convertIfNecessary就是转换逻辑,先调用ConversionService的canConvert判断能否转换,然后调用convert方法进行转换。
TypeConverterDelegate在AbstractNestablePropertyAccessor的构造方法中new了一个出来,而AbstractNestablePropertyAccessor有一个实现类,就是BeanWrapperImpl。
也就是说我们的BeanWrapper在创建的时候,就关联了一个TypeConverterDelegate,也就是关联了一个类型转换服务。
整体流程
AbstractApplicationContext -> finishBeanFactoryInitialization方法
-> 获取beanName为"conversionService" 的ConversionService Bean
-> ConfigurableBeanFactory#setConversionService(ConversionService)
-> AbstractAutowireCapableBeanFactory#doCreateBean
-> 调用createBeanInstance方法,在instantiateBean方法中,会new BeanWrapperImpl
-> 调用initBeanWrapper方法,set了conversionService
-> AbstractAutowireCapableBeanFactory.instantiateBean
-> AbstractBeanFactory#getConversionService ->
BeanDefinition -> BeanWrapper -> 属性转换(数据来源:PropertyValues)->
setPropertyValues(PropertyValues) -> TypeConverter#convertIfNecessnary ->
TypeConverterDelegate#convertIfNecessnary -> PropertyEditor or ConversionService
参考资料
极客时间-《小马哥讲 Spring 核心编程思想》