1.基于 JavaBeans 接口的类型转换实现(Spring 3.0 之前)
2.Spring 3.0+ 通用类型转换实现
场景 | 基于 JavaBeans 接口的类型转换实现 | Spring 3.0+ 通用类型转换实现 |
---|---|---|
数据绑定 | YES | YES |
BeanWrapper | YES | YES |
Bean 属性类型转换 | YES | YES |
外部化属性类型转换 | NO | YES |
第一,在数据绑定的场景中
protected void doBind(MutablePropertyValues mpvs) {
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
applyPropertyValues(mpvs);
}
其中 applyPropertyValues 方法将外部的一些配置源转换成 Bean 的属性,中间需要使用类型转换。
第二,在创建 Bean 的过程中(doGetBean)
同样也有 applyPropertyValues 方法,而且实现类似。
核心职责
扩展原理
示例
public class StringToPropertiesPropertyEditor extends PropertyEditorSupport {
// 1.实现 setAsText
public void setAsText(String text) throws java.lang.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);
}
}
测试调用
public class PropertyEditorDemo {
public static void main(String[] args) {
// 模拟 spring framework 操作
String text = "name = 小仙";
PropertyEditor propertyEditor = new StringToPropertiesPropertyEditor();
propertyEditor.setAsText(text);
System.out.printf("类型:%s,值:%s\n", propertyEditor.getValue().getClass(), propertyEditor.getValue());
}
}
执行结果:
类型:class java.util.Properties,值:{name=小仙}
转换场景 | 实现类 |
---|---|
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 |
… | … |
public class CharArrayPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(@Nullable String text) {
setValue(text != null ? text.toCharArray() : null);
}
@Override
public String getAsText() {
char[] value = (char[]) getValue();
return (value != null ? new String(value) : "");
}
}
可以看到实现还是比较简单,继承 PropertyEditorSupport 类,并覆盖 setAsText 和 getAsText 方法。
那么我们也可以改造之前 StringToPropertiesPropertyEditor 类增加 getAsText 方法
public class StringToPropertiesPropertyEditor extends PropertyEditorSupport {
private static final String ENCODING = "utf-8";
// 1.实现 setAsText
public void setAsText(String text) throws java.lang.IllegalArgumentException {
// 2.将 String 转换为 Properties
Properties properties = new Properties();
try {
Reader reader = new InputStreamReader(new ByteArrayInputStream(text.getBytes(ENCODING)));
properties.load(reader);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
// 3.临时存储 properties 对象
setValue(properties);
}
public String getAsText() {
Properties properties = (Properties) getValue();
StringBuilder textBuilder = new StringBuilder();
for (Map.Entry entry :
properties.entrySet()) {
textBuilder.append(entry.getKey()).append("=")
.append(entry.getValue())
.append(System.getProperty("line.separator"));
}
return textBuilder.toString();
}
}
测试
public class PropertyEditorDemo {
public static void main(String[] args) {
// 模拟 spring framework 操作
String text = "name = 小仙";
PropertyEditor propertyEditor = new StringToPropertiesPropertyEditor();
propertyEditor.setAsText(text);
propertyEditor.getAsText();
System.out.printf("类型:%s,值:%s\n", propertyEditor.getValue().getClass(), propertyEditor.getValue());
System.out.printf("getAsText 值:%s\n", propertyEditor.getAsText());
}
}
执行结果:
类型:class java.util.Properties,值:{name=小仙}
getAsText 值:name=小仙
扩展模式
实现 org.springframework.beans.PropertyEditorRegistrar
向 org.springframework.beans.PropertyEditorRegistry 注册自定义 PropertyEditor 实现
自定义 Company 类型的 PropertyEditor 实现,将文本转换为 Company 类型
Company
public class Company {
private String name;
private String address;
...
User 类如下
除了普通字段,我们这里加入了一个 Company 类型字段
public class User {
private Long id;
private String name;
private Integer age;
private Company company;
...
Company 类型转换的 PropertyEditor 自定义实现
/**
* {@link Company} 类型 {@link PropertyEditor} 自定义实现
*
* @author :xwf
* @date :Created in 2020\6\13 0013 19:04
* @see Company
*/
public class CompanyTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
String[] split = text.split(",");
Company company = new Company();
if (split.length > 1) {
company.setName(split[0]);
company.setAddress(split[1]);
} else {
company.setName(text);
}
setValue(company);
}
}
注册我们的自定义配置
public class CustomizedPropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
// 1.通用类型转换
// 2.Java Bean 属性类型转换
registry.registerCustomEditor(Company.class, new CompanyTypeEditor());
}
}
将注册类声明为 Spring Bean,通过 XML 的方式,也可以通过注解
resources/META-INF 目录下 property-editors-context.xml 配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
https://www.springframework.org/schema/util/spring-util.xsd">
<bean id="customPropertyEditorRegistrar" class="com.huajie.thinking.in.spring.conversion.CustomizedPropertyEditorRegistrar"/>
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
list>
property>
bean>
<bean id="user" class="com.huajie.thinking.in.spring.conversion.domain.User">
<property name="name" value="xwf"/>
<property name="age" value="18"/>
<property name="company" value="alibaba,wuhan"/>
bean>
beans>
测试
public class SpringCustomizedPropertyEditorDemo {
public static void main(String[] args) {
// 创建并启动 BeanFactory 容器
ConfigurableApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath:/META-INF/property-editors-context.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println(user);
applicationContext.close();
}
}
执行结果:
User{id=null, name='xwf', age=18, company=Company{name='alibaba', address='wuhan'}}
可以看到
由文本的方式被转化为 Company 类型。
1.违反单一职责原则
2.java.beans.PropertyEditor 实现类型局限
3.java.beans.PropertyEditor 实现缺少类型安全
为了解决上面 PropertyEditor 的一些问题和缺陷,从 Spring 3 开始,引入了通用类型转换接口,基于 JDK1.5 的泛型进行设计和改良
类型转换接口 - org.springframework.core.convert.converter.Converter
匹配条件 - org.springframework.core.convert.converter.ConditionalConverter
通用类型转换接口 - org.springframework.core.convert.converter.GenericConverter
内建扩展分为三类,分别放在三个包下面
转换场景 | 实现类所在包名(package) |
---|---|
日期/时间相隔 | org.springframework.format.datetime |
Java 8 日期/时间相关 | org.springframework.format.datetime.standard |
通用实现 | org.springframework.core.convert.support |
部分源码截图
从命名可以看出,这些内建的实现,并不局限于 String 转其他类型。
局限一:缺少 Source Type 和 Target Type 前置判断
局限二:仅能转换单一的 Source Type 和 Target Type
org.springframework.core.convert.converter.GenericConverter
核心要素 | 说明 |
---|---|
使用场景 | 用于“复合”类型转换场景,比如 Collection、Map、数组等 |
转换范围 | Set |
配对类型 | org.springframework.core.convert.converter.GenericConverter.ConvertiblePair |
转换方法 | Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) |
类型描述 | org.springframework.core.convert.TypeDescriptor |
GenericConverter 局限性
GenericConverter 接口优化 - ConditionalGenericConverter
第一种扩展方式:实现转换器接口
第二种扩展方式:注册转换器实现
自定义类实现 Company 类型转化为 String 类型,实现 ConditionalGenericConverter 接口
public class CompanyToStringConverter implements ConditionalGenericConverter {
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return Company.class.equals(sourceType.getObjectType()) &&
String.class.equals(targetType.getObjectType());
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Company.class, String.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Company company = Company.class.cast(source);
return company.getName() + "-" + company.getAddress();
}
}
修改 property-editors-context.xml 文件
conversionService
<bean id="user" class="com.huajie.thinking.in.spring.conversion.domain.User">
<property name="name" value="xwf"/>
<property name="age" value="18"/>
<property name="company" value="alibaba,wuhan"/>
<property name="companyAsText" ref="company"/>
bean>
<bean id="company" class="com.huajie.thinking.in.spring.conversion.domain.Company">
<property name="name" value="alimama"/>
<property name="address" value="hangzhou"/>
bean>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.huajie.thinking.in.spring.conversion.CompanyToStringConverter"/>
set>
property>
bean>
测试:
public class SpringCustomizedPropertyEditorDemo {
public static void main(String[] args) {
// 创建并启动 BeanFactory 容器
ConfigurableApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath:/META-INF/property-editors-context.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println(user);
applicationContext.close();
}
}
执行结果:
User{id=null, name='xwf', age=18, company=Company{name='alibaba', address='wuhan'}, companyAsText=alimama-hangzhou}
可以看到 companyAsText 的属性为 alimama-hangzhou,和我们的 convert 实现一样将 name 和 address 进行拼接。
实现类型 | 说明 |
---|---|
GenericConversionService | 通用 ConversionService 模板实现,不内置转换器实现 |
DefaultConversionService | 基础 ConversionService 实现,内置常用转换器实现 |
FormattingConversionService | 通用 Formatter + GeneircConversionService 实现,不内置 Formatter 实现和转换器实现 |
DefaultFormattingConversionService | DefaultConversionService + 格式化实现 |
类型转换器底层接口 - org.springframework.beans.TypeConverter
类型转换器底层抽象实现 - org.springframework.beans.TypeConverterSupport
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
...
通过 name=“conversionService” + ConversionService.class 类型的方式依赖查找 ConversionService Bean
beanFactory.setConversionService 将第一步查找到的 ConversionService Bean 对象设置到当前 BeanFactory 应用上下文的 conversionService
属性中
接着第一步的调用链路继续往下
ConfigurableListableBeanFactory#preInstantiateSingletons
-> AbstractBeanFactory#getBean
-> AbstractBeanFactory#doGetBean
-> AbstractAutowireCapableBeanFactory#doCreateBean
-> AbstractAutowireCapableBeanFactory#instantiateBean
-> AbstractBeanFactory#initBeanWrapper
protected void initBeanWrapper(BeanWrapper bw) {
bw.setConversionService(getConversionService());
registerCustomEditors(bw);
}
-> AbstractBeanFactory#getConversionService
-> AbstractAutowireCapableBeanFactory#populateBean //属性赋值 -> 转换(数据来源:PropertyValues)
-> AbstractPropertyAccessor#setPropertyValues
-> TypeConverter#convertIfNecessary
-> TypeConverterDelegate#convertIfNecessary
-> PropertyEditor or ConversionService
1.基于 JavaBeans PropertyEditor 接口实现
2.Spring 3.0+ 通用类型转换实现,16.2 中的 4 个接口