赞
踩
本文主要内容是Spring Bean通过xml等配置进行属性映射时,发生的类型转换的基本原理与源码分析。
Spring 3.0以前,基于 JavaBeans 接口的类型转换实现,基于 java.beans.PropertyEditor 接口扩展。
Spring 3.0+ 通用类型转换实现。
场景 | 基于 JavaBeans 接口的类型转换实现 | Spring 3.0+ 通用类型转换实现 |
---|---|---|
数据绑定 | YES | YES |
BeanWrapper | YES | YES |
Bean 属性类型装换 | YES | YES |
外部化属性类型转换 | NO | YES |
我们之前提到过: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属性绑定时发生的类型转换。
核心职责:将 String 类型的内容转化为目标类型的对象
扩展原理:
• Spring 框架将文本内容传递到 PropertyEditor 实现的 setAsText(String) 方法
• PropertyEditor#setAsText(String) 方法实现将 String 类型转化为目标类型的对象
• 将目标类型的对象传入 PropertyEditor#setValue(Object) 方法
• PropertyEditor#setValue(Object) 方法实现需要临时存储传入对象
• Spring 框架将通过 PropertyEditor#getValue() 获取类型转换后的对象
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());
內建扩展(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 |
… | … |
我们可以看出,基本也都是继承了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) : "");
}
}
扩展模式: 扩展 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;
Spring3之后弃用了PropertyEditor ,PropertyEditor 的设计缺陷主要有以下三种:
1、违反职责单一原则
• java.beans.PropertyEditor 接口职责太多,除了类型转换,还包括 Java Beans 事件和 Java GUI 交互
2、java.beans.PropertyEditor 实现类型局限
• 来源类型只能为 java.lang.String 类型
3、java.beans.PropertyEditor 实现缺少类型安全
• 除了实现类命名可以表达语义,实现类无法感知目标转换类型
类型转换接口 - 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()); } } }
转换场景 | 实现类所在包名(package) |
---|---|
日期/时间相关 | org.springframework.format.datetime |
Java 8 日期/时间相关 | org.springframework.format.datetime.standard |
通用实现 | org.springframework.core.convert.support |
通过这些源码来看,使用方式与上面的PropertyEditor方式有着较大的差别(篇幅限制,大家自行翻阅源码)。
Converter 只提供了一个T convert(S source);方法,输入一个参数返回一个参数,没有判断是否支持这个参数类型的转换。
应对:增加 org.springframework.core.convert.converter.ConditionalConverter 实现,同时实现Converter、ConditionalConverter 接口,其中ConditionalConverter 接口的matches方法可以判断是否支持该类型参数的转换。
Converter 只提供了一个T convert(S source);方法,输入一个参数返回一个参数,对集合类型的参数转换不友好。
应对:使用 org.springframework.core.convert.converter.GenericConverter 代替,GenericConverter 接口我们上面也分析到,用一个内部类ConvertiblePair可以实现集合类型转换。
实际上,Converter和GenericConverter 会相互配合,简单类型的转换一般使用Converter,复合类型的转换一般使用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 |
与Converter接口一样,GenericConverter接口也缺少 Source Type 和 Target Type 前置判断。
同时,GenericConverter接口单一类型转换实现复杂,也就是说,GenericConverter比较适合复合类型的转换,Converter接口比较适合单一类型的转换。
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; } }
实现转换器接口
• 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>
通过类型转换的源码我们看到,类型转换都需要使用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,在这个基础上做了一些扩展。
在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贯穿了整个上下文。
那么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中。
类型转换器底层接口 - 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接口在Spring2.0时就已经存在了。
TypeConverter接口有三个重载方法:convertIfNecessary,顾名思义,就是如果能转的时候就会转换。
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的核心方法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 核心编程思想》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。