Spring 类型转换、数值绑定与验证(二)—PropertyEditor与Conversion

 Spring 中,属性类型转换是在将数值绑定到目标对象时完成的。例如在创建ApplicationContext 容器时,将XML配置的bean 转换成Java类型对象,主要是借助了PropertyEditor类,而在Spring MVC 的Controller的请求参数转化为特定类型时,我们也可以自定义转化器Convert并注册来完成转换。以下是Spring相关源码分析。

1 PropertyEditor

JDK自带的接口。支持各种不同的方式来显示和更新特性值。
xml 配置Bean 或者@Value 赋值到Bean的时候,属性字段值很多是文本类型的字符串,但是属性的类型却可能是Integer,File等类型,PropertyEditor 就是用来把文本型的值转换为对应类型值的工具。 其关键方法是void setAsTest(String text)。

PropertyEditorSupport 是JDK提供的默认自定义,各种自定义的PropertyEditor大多是继承该类来实现。重写setAsText方法。

1.1 PropertyEditorRegistry

PropertyEditor 注册表,提供了用于注册并管理PropertyEditor的接口。

PropertyEditorRegistrySupport 是其默认实现,创建并注册了一些默认的PropertyEditor,并增加了对类型转换Convertion的支持。

图 PropertyEditorRegistrySupport UML

defaultEditors 及 customEditors 分别用来存储注册的默认及自定义editors。而overriddenDefaultEditors 是用来存储覆盖默认的editors。对应方法为overrideDefaultEditor。

public void overrideDefaultEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
   if (this.overriddenDefaultEditors == null) {
      this.overriddenDefaultEditors = new HashMap<>();
   }
   this.overriddenDefaultEditors.put(requiredType, propertyEditor);
}

注册器的类型编辑器覆盖顺序为: 自定义editors -> ConversionService -> 覆盖默认editors -> 默认editors。 defaultEditors 最先被覆盖。boolean类型遍历configValueEditorsActive 用来控制在创建默认editors时,是否需要创建用于配置的editor(这类editor通常不适合用于数据绑定)。

图 createDefaultEditors 方法的部分截图

customEditorsForPath 是用来存储为特定属性路径注册的editor。

图 registerCustomEditor 方法

1.2 PropertyEditorRegistrar

PropertyEditor 注册器,用于把editors 注册到给定的registry中。

ResourceEditorRegistrar 是其默认实现。通常在Spring容器初始化时被调用。

图 ResourceEditorRegistrar UML

1.3 CustomEditorConfigurer

用于注册自定义Editor。可以在XML 或者使用注册来创建这个bean,并设置自定义editor属性。

图 CustomEditorConfigurer UML

<bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"> 

        <!-- 设置 customEditors 属性 --> 

        <property name="customEditors"> 

            <map> 

                <!-- 注册自定义的日期编辑器 --> 

                <entry key="java.util.Date"> 

                    <bean class="org.springframework.beans.propertyeditors.CustomDateEditor"> 

                        <!-- 设置日期格式 --> 

                        <constructor-arg value="yyyy-MM-dd"/> 

                        <!-- 设置是否允许空值 --> 

                        <constructor-arg value="false"/> 

                    </bean> 

                </entry> 

                <!-- 可以添加更多的自定义编辑器 --> 

            </map> 

        </property> 

 </bean> 

2 Conversion

可以替代PropertyEditor,主要用于在绑定数值时,将源类型转换为目标类型。

Spring 定义的Converter<S,T>接口,只定义了一个方法:

T convert(S source); // 将源类型转换为目标类型。

ConverterFactory<S,R> 接口,定义了一个工厂方法用于创建Converter实例:

<T extends R> Converter<S, T> getConverter(Class<T> targetType);

2.1 GenericConverter

支持在多个不同的源类型和目标类型之间进行转换。使用场景有:将不同类型的数值转换为集合,或者根据字段上的注解或泛型信息来驱动类型转换。

图 GenericConverter接口 UML

ConveriblePair 保存源类型及目标类型。

getConvertibleTypes 返回可以被转换的类型对(源类型与目标类型)。

ConditionalConverter 用于判断源类型是否可以转换的接口。

public interface ConditionalConverter {
    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

以下是Spring内部实现了GenericConverter及ConditionalConverter 的接口的ArrayToCollectionConverter(内部类,不对外部使用)源代码:

final class ArrayToCollectionConverter implements ConditionalGenericConverter {

	private final ConversionService conversionService;


	public ArrayToCollectionConverter(ConversionService conversionService) {
		this.conversionService = conversionService;
	}


	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(Object[].class, Collection.class));
	}

	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		return ConversionUtils.canConvertElements(
				sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService);
	}

	@Override
	@Nullable
	public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		if (source == null) {
			return null;
		}

		int length = Array.getLength(source);
		TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
		Collection<Object> target = CollectionFactory.createCollection(targetType.getType(),
				(elementDesc != null ? elementDesc.getType() : null), length);

		if (elementDesc == null) {
			for (int i = 0; i < length; i++) {
				Object sourceElement = Array.get(source, i);
				target.add(sourceElement);
			}
		}
		else {
			for (int i = 0; i < length; i++) {
				Object sourceElement = Array.get(source, i);
				Object targetElement = this.conversionService.convert(sourceElement,
						sourceType.elementTypeDescriptor(sourceElement), elementDesc);
				target.add(targetElement);
			}
		}
		return target;
	}

}

2.2 ConversionService 与 ConverterRegistry

ConversionService定义了在运行期间执行转换的统一接口。

ConversionRegistry  Converter注册器,定义了用于添加/删除转换器的方法。

GenericConversionService 同时实现了这两个接口。而DefaultConversionService 继承了这个类,并增加了两个静态方法:

getSharedInstance(): 创建一个共享的DefaultConversionServices单例。

addDefaultConverters(ConverterRegistry converterRegistry):创建并注册一些默认的转换器。

2.3 ConversionServiceFactoryBean

用于添加自定义转换器的Bean。

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> 

    <!-- 在这里配置你的类型转换器 --> 

    <property name="converters"> 

        <list> 

            <bean class="com.example.MyCustomConverter"/> 

            <!-- 其他转换器 --> 

        </list> 

    </property> 

</bean>

其源代码如下:

public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {

	@Nullable
	private Set<?> converters;

	@Nullable
	private GenericConversionService conversionService;


	/**
	 * Configure the set of custom converter objects that should be added:
	 * implementing {@link org.springframework.core.convert.converter.Converter},
	 * {@link org.springframework.core.convert.converter.ConverterFactory},
	 * or {@link org.springframework.core.convert.converter.GenericConverter}.
	 */
	public void setConverters(Set<?> converters) {
		this.converters = converters;
	}

	@Override
	public void afterPropertiesSet() {
		this.conversionService = createConversionService();
		ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
	}

	/**
	 * Create the ConversionService instance returned by this factory bean.
	 * <p>Creates a simple {@link GenericConversionService} instance by default.
	 * Subclasses may override to customize the ConversionService instance that
	 * gets created.
	 */
	protected GenericConversionService createConversionService() {
		return new DefaultConversionService();
	}


	// implementing FactoryBean

	@Override
	@Nullable
	public ConversionService getObject() {
		return this.conversionService;
	}

	@Override
	public Class<? extends ConversionService> getObjectType() {
		return GenericConversionService.class;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

}
  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值