夯实Spring系列|第二十章:Spring 类型转换(Type Conversion)

夯实Spring系列|第二十章:Spring 类型转换(Type Conversion)

1.项目环境

2.Spring 类型转换的实现

1.基于 JavaBeans 接口的类型转换实现(Spring 3.0 之前)

  • 基于 java.beans.PropertyEditor 接口扩展

2.Spring 3.0+ 通用类型转换实现

3.使用场景

场景基于 JavaBeans 接口的类型转换实现Spring 3.0+ 通用类型转换实现
数据绑定YESYES
BeanWrapperYESYES
Bean 属性类型转换YESYES
外部化属性类型转换NOYES

第一,在数据绑定的场景中

  • org.springframework.validation.DataBinder#doBind
	protected void doBind(MutablePropertyValues mpvs) {
		checkAllowedFields(mpvs);
		checkRequiredFields(mpvs);
		applyPropertyValues(mpvs);
	}

其中 applyPropertyValues 方法将外部的一些配置源转换成 Bean 的属性,中间需要使用类型转换。

第二,在创建 Bean 的过程中(doGetBean)

  • AbstractAutowireCapableBeanFactory#doCreateBean
    • AbstractAutowireCapableBeanFactory#populateBean
      • AbstractAutowireCapableBeanFactory#applyPropertyValues

同样也有 applyPropertyValues 方法,而且实现类似。

4.基于 JavaBeans 接口的类型转换

核心职责

  • 将 String 类型的内容转化为目标类型的对象

扩展原理

  • Spring 框架将文本内容传递到 PropertyEditor 实现的 setAsText(String) 方法
  • PropertyEditor#setAsText(String) 方法实现将 String 类型转化为目标类型的对象
  • 将目标类型的对象传入 PropertyEditor#setValue(Object) 方法
  • PropertyEditor#setValue(Object) 方法实现需要临时存储传入对象
  • Spring 框架将通过 PropertyEditor#getValue() 获取类型转换后的对象

示例

  • PropertyEditor 实现,一般继承 PropertyEditorSupport 进行实现
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=小仙}

5.Spring 内建 PropertyEditor 扩展

转换场景实现类
String - > Byte 数组org.springframework.beans.propertyeditors.ByteArrayPropertyEditor
String -> Charorg.springframework.beans.propertyeditors.CharacterEditor
String -> Char 数组org.springframework.beans.propertyeditors.CharArrayPropertyEditor
String -> Charsetorg.springframework.beans.propertyeditors.CharsetEditor
String -> Classorg.springframework.beans.propertyeditors.ClassEditor
String -> Currencyorg.springframework.beans.propertyeditors.CurrencyEditor
  • CharArrayPropertyEditor 源码
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=小仙

6.自定义 PropertyEditor 扩展

扩展模式

  • 扩展 java.beans.PropertyEditorSupport 类

实现 org.springframework.beans.PropertyEditorRegistrar

  • 实现 org.springframework.beans.PropertyEditorRegistrar#registerCustomEditors 方法
  • 将 PropertyEditorRegistrar 实现注册为 Spring Bean

向 org.springframework.beans.PropertyEditorRegistry 注册自定义 PropertyEditor 实现

  • 通用类型实现 registerCustomEditor(java.lang.Class<?>, java.beans.PropertyEditor)
  • Java Bean 属性类型实现:registerCustomEditor(java.lang.Class<?>, java.lang.String, java.beans.PropertyEditor)

6.1 示例

自定义 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 配置

<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!-- 3.将其声明为 Spring Bean-->
    <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'}}

可以看到 <property name="company" value="alibaba,wuhan"/>

由文本的方式被转化为 Company 类型。

7.Spring PropertyEditor 的设计缺陷

1.违反单一职责原则

  • java.beans.PropertyEditor 接口职责太多,除了类型转换,还包括 Java Beans 事件和 Java GUI 交互

2.java.beans.PropertyEditor 实现类型局限

  • 来源类型只能为 java.lang.String 类型
    • 从上面的例子可以看出使用并不是很方便

3.java.beans.PropertyEditor 实现缺少类型安全

  • 处理实现类命名可以表达语义,实现类无法感知目标转换类型
    • 使用 Object 作为返回类型,编程时没有强类型约束,运行时可能会出异常

8.Spring 3 通用类型转换接口

为了解决上面 PropertyEditor 的一些问题和缺陷,从 Spring 3 开始,引入了通用类型转换接口,基于 JDK1.5 的泛型进行设计和改良

类型转换接口 - org.springframework.core.convert.converter.Converter<S, T>

  • 泛型参数 S:来源类型(Source),参数 T:目标类型(Target)
  • 核心方法:T convert(S source)

匹配条件 - org.springframework.core.convert.converter.ConditionalConverter

  • boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType)
  • 判断传入类型和目标类型是否能匹配到转换器

通用类型转换接口 - org.springframework.core.convert.converter.GenericConverter

  • 核心方法:Object convert( Object source, TypeDescriptor sourceType, TypeDescriptor targetType)
  • 配对类型:org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
    • 多类型的匹配
  • 类型描述:org.springframework.core.convert.TypeDescriptor

9.Spring 内建类型转换器

内建扩展分为三类,分别放在三个包下面

转换场景实现类所在包名(package)
日期/时间相隔org.springframework.format.datetime
Java 8 日期/时间相关org.springframework.format.datetime.standard
通用实现org.springframework.core.convert.support

部分源码截图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pamRyDqo-1593305192568)(G:\workspace\learn-document\spring-framework\csdn\image-20200613202504892.png)]
从命名可以看出,这些内建的实现,并不局限于 String 转其他类型。

10.Converter 接口的局限性

局限一:缺少 Source Type 和 Target Type 前置判断

  • 应对:增加 org.springframework.core.convert.converter.ConditionalConverter

局限二:仅能转换单一的 Source Type 和 Target Type

  • 应对:使用 org.springframework.core.convert.converter.GenericConverter

11.GenericConverter 接口

org.springframework.core.convert.converter.GenericConverter

核心要素说明
使用场景用于“复合”类型转换场景,比如 Collection、Map、数组等
转换范围Set<ConvertiblePair> getConvertibleTypes()
配对类型org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
转换方法Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)
类型描述org.springframework.core.convert.TypeDescriptor

12.优化 GenericConverter 接口

GenericConverter 局限性

  • 缺少 Source Type 和 Target Type 前置判断
  • 单一类型转换实现复杂

GenericConverter 接口优化 - ConditionalGenericConverter

  • 复合类型转换:org.springframework.core.convert.converter.GenericConverter
  • 类型条件判断:org.springframework.core.convert.converter.ConditionalGenericConverter

13.扩展 Spring 类型转换器

13.1 扩展方式

第一种扩展方式:实现转换器接口

  • org.springframework.core.convert.converter.Converter
  • org.springframework.core.convert.converter.ConverterFactory
  • org.springframework.core.convert.converter.GenericConverter

第二种扩展方式:注册转换器实现

  • org.springframework.core.convert.support.ConversionServiceFactory
  • org.springframework.context.support.ConversionServiceFactoryBean
  • org.springframework.core.convert.ConversionService

13.2 示例

自定义类实现 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 文件

  • user 中新增 companyAsText 属性,ref 指向 company Bean
  • 新增 company Bean 的配置
  • 声明 ConversionServiceFactoryBean,将自定义的 CompanyToStringConverter 配置为 ConversionServiceFactoryBean 的属性 converters,这个属性为 Set 集合
    • 注意 Xml 中 Bean 的配置 id 必须等于 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 进行拼接。

14.统一类型转换服务

实现类型说明
GenericConversionService通用 ConversionService 模板实现,不内置转换器实现
DefaultConversionService基础 ConversionService 实现,内置常用转换器实现
FormattingConversionService通用 Formatter + GeneircConversionService 实现,不内置 Formatter 实现和转换器实现
DefaultFormattingConversionServiceDefaultConversionService + 格式化实现

15.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

15.1 源码分析

  1. 通过 name =“conversionService” 依赖查找
  • AbstractApplicationContext#refresh //应用上下文启动
    • AbstractApplicationContext#finishBeanFactoryInitialization //BeanFactory初始化完成方法
	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

  1. BeanFactory 应用上下文设置 conversionService 属性

beanFactory.setConversionService 将第一步查找到的 ConversionService Bean 对象设置到当前 BeanFactory 应用上下文的 conversionService 属性中

  1. 应用上下启动中的 finishBeanFactoryInitialization 阶段

接着第一步的调用链路继续往下

ConfigurableListableBeanFactory#preInstantiateSingletons

-> AbstractBeanFactory#getBean

-> AbstractBeanFactory#doGetBean

-> AbstractAutowireCapableBeanFactory#doCreateBean

-> AbstractAutowireCapableBeanFactory#instantiateBean

-> AbstractBeanFactory#initBeanWrapper

	protected void initBeanWrapper(BeanWrapper bw) {
		bw.setConversionService(getConversionService());
		registerCustomEditors(bw);
	}

-> AbstractBeanFactory#getConversionService

  • 这里的 getConversionService 获取的就是第二步中设置的 ConversionService Bean 对象
  • 然后将这个 ConversionService 对象传到 BeanWapper 这个对象中
  1. Bean (populateBean)属性赋值阶段中完成类型转换

-> AbstractAutowireCapableBeanFactory#populateBean //属性赋值 -> 转换(数据来源:PropertyValues)

-> AbstractPropertyAccessor#setPropertyValues

-> TypeConverter#convertIfNecessary

-> TypeConverterDelegate#convertIfNecessary

-> PropertyEditor or ConversionService

16.面试题

16.1 Spring 类型转换实现有哪些?

1.基于 JavaBeans PropertyEditor 接口实现

2.Spring 3.0+ 通用类型转换实现,16.2 中的 4 个接口

16.2 Spring 类型转换器接口有哪些?

  • 类型转换接口 - org.springframework.core.convert.converter.Converter
  • 通用类型转换接口 - org.springframework.core.convert.converter.GenericConverter
  • 类型条件接口 - org.springframework.core.convert.converter.ConditionalConverter
  • 综合类型接口 - org.springframework.core.convert.converter.ConditionalGenericConverter

17.参考

  • 极客时间-小马哥《小马哥讲Spring核心编程思想》
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值