文章目录

  • 一、概述
  • 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 核心编程思想》