Spring3.1.2 提供的类型转换支持

Spring3.* 引入了更加通用的类型转换系统,其定义了SPI接口(Converter等)和相应的运行时执行类型转换的API(ConversionService等),在Spring中它和PropertyEditor功能类似,可以替代PropertyEditor来转换外部Bean属性的值到Bean属性需要的类型。

该类型转换系统是Spring通用的,其定义在org.springframework.core.convert包中,不仅仅在Spring Web MVC场景下。目标是完全替换PropertyEditor,提供无状态、强类型且可以在任意类型之间转换的类型转换系统,可以用于任何需要的地方,如SpEL、数据绑定。

Converter SPI完成通用的类型转换逻辑,如java.util.Date<---->java.lang.Long或java.lang.String---->PhoneNumberModel等。

1、类型转换器:提供类型转换的实现支持。

一个有如下三种接口:

(1、Converter:类型转换器,用于转换S类型到T类型,此接口的实现必须是线程安全的且可以被共享。

Java代码
package org.springframework.core.convert.converter;

/**
 * A converter converts a source object of type S to a target of type T.
 * Implementations of this interface are thread-safe and can be shared.
 *
 * @author Keith Donald
 * @since 3.0
 */
public interface Converter<S, T> {

	/**
	 * Convert the source of type S to target type T.
	 * @param source the source object to convert, which must be an instance of S
	 * @return the converted object, which must be an instance of T
	 * @throws IllegalArgumentException if the source could not be converted to the desired target type
	 */
	T convert(S source);

}

 示例:请参考cn.javass.chapter7.converter.support.StringToPhoneNumberConverter转换器,用于将String--->PhoneNumberModel。

此处我们可以看到Converter接口实现只能转换一种类型到另一种类型,不能进行多类型转换,如将一个数组转换成集合,如(String[] ----> List<String>、String[]----->List<PhoneNumberModel>等)。

(2、GenericConverter和ConditionalGenericConverter:GenericConverter接口实现能在多种类型之间进行转换,ConditionalGenericConverter是有条件的在多种类型之间进行转换

Java代码  
/*
 * Copyright 2002-2011 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.convert.converter;

import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.Assert;

import java.util.Set;

/**
 * Generic converter interface for converting between two or more types.
 *
 * <p>This is the most flexible of the Converter SPI interfaces, but also the most complex.
 * It is flexible in that a GenericConverter may support converting between multiple source/target
 * type pairs (see {@link #getConvertibleTypes()}. In addition, GenericConverter implementations
 * have access to source/target {@link TypeDescriptor field context} during the type conversion process.
 * This allows for resolving source and target field metadata such as annotations and generics
 * information, which can be used influence the conversion logic.
 *
 * <p>This interface should generally not be used when the simpler {@link Converter} or
 * {@link ConverterFactory} interfaces are sufficient.
 *
 * @author Keith Donald
 * @author Juergen Hoeller
 * @since 3.0
 * @see TypeDescriptor
 * @see Converter
 * @see ConverterFactory
 */
public interface GenericConverter {

	/**
	 * Return the source and target types which this converter can convert between.
	 * <p>Each entry is a convertible source-to-target type pair.
	 */
	Set<ConvertiblePair> getConvertibleTypes();

	/**
	 * Convert the source to the targetType described by the TypeDescriptor.
	 * @param source the source object to convert (may be null)
	 * @param sourceType the type descriptor of the field we are converting from
	 * @param targetType the type descriptor of the field we are converting to
	 * @return the converted object
	 */
	Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);


	/**
	 * Holder for a source-to-target class pair.
	 */
	public static 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(Object obj) {
            if (this == obj) {
				return true;
			}
            if (obj == null || obj.getClass() != ConvertiblePair.class) {
				return false;
			}
            ConvertiblePair other = (ConvertiblePair) obj;
            return this.sourceType.equals(other.sourceType) && this.targetType.equals(other.targetType);

        }

        @Override
        public int hashCode() {
            return this.sourceType.hashCode() * 31 + this.targetType.hashCode();
        }
    }

}

getConvertibleTypes:指定了可以转换的目标类型对;

convert:在sourceType和targetType类型之间进行转换。

Java代码
/*
 * Copyright 2002-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.convert.converter;

import org.springframework.core.convert.TypeDescriptor;

/**
 * A generic converter that conditionally executes.
 *
 * <p>Applies a rule that determines if a converter between a set of
 * {@link #getConvertibleTypes() convertible types} matches given a client request to
 * convert between a source field of convertible type S and a target field of convertible type T.
 *
 * <p>Often used to selectively match custom conversion logic based on the presence of
 * a field or class-level characteristic, such as an annotation or method. For example,
 * when converting from a String field to a Date field, an implementation might return
 * <code>true</code> if the target field has also been annotated with <code>@DateTimeFormat</code>.
 *
 * <p>As another example, when converting from a String field to an Account field,
 * an implementation might return true if the target Account class defines a
 * <code>public static findAccount(String)</code> method.
 *
 * @author Keith Donald
 * @since 3.0
 */
public interface ConditionalGenericConverter extends GenericConverter {

	/**
	 * Should the converter from <code>sourceType</code> to <code>targetType</code>
	 * currently under consideration be selected?
	 * @param sourceType the type descriptor of the field we are converting from
	 * @param targetType the type descriptor of the field we are converting to
	 * @return true if conversion should be performed, false otherwise
	 */
	boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
	
}

 matches:用于判断sourceType和targetType类型之间能否进行类型转换。

示例:如org.springframework.core.convert.support.ArrayToCollectionConverter和CollectionToArrayConverter用于在数组和集合间进行转换的ConditionalGenericConverter实现,如在String[]<---->List<String>、String[]<---->List<PhoneNumberModel>等之间进行类型转换。

对于我们大部分用户来说一般不需要自定义GenericConverter, 如果需要可以参考内置的GenericConverter来实现自己的。

(3、ConverterFactory:工厂模式的实现,用于选择将一种S源类型转换为R类型的子类型T的转换器的工厂接口。

Java代码:
/*
 * Copyright 2002-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.convert.converter;

/**
 * A factory for "ranged" converters that can convert objects from S to subtypes of R.
 *
 * @author Keith Donald
 * @since 3.0 
 * @param <S> The source type converters created by this factory can convert from
 * @param <R> The target range (or base) type converters created by this factory can convert to;
 * for example {@link Number} for a set of number subtypes.
 */
public interface ConverterFactory<S, R> {

	/**
	 * Get the converter to convert from S to target type T, where T is also an instance of R.
	 * @param <T> the target type
	 * @param targetType the target type to convert to
	 * @return A converter from S to T
	 */
	<T extends R> Converter<S, T> getConverter(Class<T> targetType);

}

 S:源类型;R目标类型的父类型;T:目标类型,且是R类型的子类型;

getConverter:得到目标类型的对应的转换器。

示例:如org.springframework.core.convert.support.NumberToNumberConverterFactory用于在Number类型子类型之间进行转换,如Integer--->Double, Byte---->Integer, Float--->Double等。

对于我们大部分用户来说一般不需要自定义ConverterFactory,如果需要可以参考内置的ConverterFactory来实现自己的。

2、类型转换器注册器、类型转换服务:提供类型转换器注册支持,运行时类型转换API支持。

一共有如下两种接口:

(1、ConverterRegistry:类型转换器注册支持,可以注册/删除相应的类型转换器。

Java代码
/*
 * Copyright 2002-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.convert.converter;

/**
 * For registering converters with a type conversion system.
 *
 * @author Keith Donald
 * @author Juergen Hoeller
 * @since 3.0
 */
public interface ConverterRegistry {
	
	/**
	 * Add a plain converter to this registry.
	 * The convertible sourceType/targetType pair is derived from the Converter's parameterized types.
	 * @throws IllegalArgumentException if the parameterized types could not be resolved
	 */
	void addConverter(Converter<?, ?> converter);

	/**
	 * Add a plain converter to this registry.
	 * The convertible sourceType/targetType pair is specified explicitly.
	 * Allows for a Converter to be reused for multiple distinct pairs without having to create a Converter class for each pair.
	 * @since 3.1
	 */
	void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);

	/**
	 * Add a generic converter to this registry.
	 */
	void addConverter(GenericConverter converter);
	
	/**
	 * Add a ranged converter factory to this registry.
	 * The convertible sourceType/rangeType pair is derived from the ConverterFactory's parameterized types.
	 * @throws IllegalArgumentException if the parameterized types could not be resolved. 
	 */
	void addConverterFactory(ConverterFactory<?, ?> converterFactory);

	/**
	 * Remove any converters from sourceType to targetType.
	 * @param sourceType the source type
	 * @param targetType the target type
	 */
	void removeConvertible(Class<?> sourceType, Class<?> targetType);

}
 可以注册:Converter实现,GenericConverter实现,ConverterFactory实现。

 

(2、ConversionService:运行时类型转换服务接口,提供运行期类型转换的支持。

Java代码   
/*
 * Copyright 2002-2011 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.convert;

/**
 * A service interface for type conversion. This is the entry point into the convert system.
 * Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system.
 *
 * @author Keith Donald
 * @since 3.0
 */
public interface ConversionService {

	/**
	 * Returns true if objects of sourceType can be converted to targetType.
	 * If this method returns true, it means {@link #convert(Object, Class)} is capable of converting an instance of sourceType to targetType.
	 * Special note on collections, arrays, and maps types:
	 * For conversion between collection, array, and map types, this method will return 'true'
	 * even though a convert invocation may still generate a {@link ConversionException} if the underlying elements are not convertible.
	 * Callers are expected to handle this exceptional case when working with collections and maps.
	 * @param sourceType the source type to convert from (may be null if source is null)
	 * @param targetType the target type to convert to (required)
	 * @return true if a conversion can be performed, false if not
	 * @throws IllegalArgumentException if targetType is null
	 */
	boolean canConvert(Class<?> sourceType, Class<?> targetType);

	/**
	 * Returns true if objects of sourceType can be converted to the targetType.
	 * The TypeDescriptors provide additional context about the source and target locations where conversion would occur, often object fields or property locations.
	 * If this method returns true, it means {@link #convert(Object, TypeDescriptor, TypeDescriptor)} is capable of converting an instance of sourceType to targetType.
	 * Special note on collections, arrays, and maps types:
	 * For conversion between collection, array, and map types, this method will return 'true'
	 * even though a convert invocation may still generate a {@link ConversionException} if the underlying elements are not convertible.
	 * Callers are expected to handle this exceptional case when working with collections and maps.
	 * @param sourceType context about the source type to convert from (may be null if source is null)
	 * @param targetType context about the target type to convert to (required)
	 * @return true if a conversion can be performed between the source and target types, false if not
	 * @throws IllegalArgumentException if targetType is null
	 */
	boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

	/**
	 * Convert the source to targetType.
	 * @param source the source object to convert (may be null)
	 * @param targetType the target type to convert to (required)
	 * @return the converted object, an instance of targetType
	 * @throws ConversionException if a conversion exception occurred
	 * @throws IllegalArgumentException if targetType is null
	 */
	<T> T convert(Object source, Class<T> targetType);
	
	/**
	 * Convert the source to targetType.
	 * The TypeDescriptors provide additional context about the source and target locations where conversion will occur, often object fields or property locations.
	 * @param source the source object to convert (may be null)
	 * @param sourceType context about the source type converting from (may be null if source is null)
	 * @param targetType context about the target type to convert to (required)
	 * @return the converted object, an instance of {@link TypeDescriptor#getObjectType() targetType}</code>
	 * @throws ConversionException if a conversion exception occurred
	 * @throws IllegalArgumentException if targetType is null
	 * @throws IllegalArgumentException if sourceType is null but source is not null
	 */
	Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}

 convert:将源对象转换为目标类型的目标对象。

Spring提供了两个默认实现(其都实现了ConverterRegistry、ConversionService接口):

DefaultConversionService:默认的类型转换服务实现;

DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可。

7.2.2.2、Spring内建的类型转换器如下所示:   

类名

说明

第一组:标量转换器

StringToBooleanConverter

String----->Boolean

true:true/on/yes/1; false:false/off/no/0

ObjectToStringConverter

Object----->String

调用toString方法转换

StringToNumberConverterFactory

String----->Number(如Integer、Long等)

NumberToNumberConverterFactory

Number子类型(Integer、Long、Double等)<——> Number子类型(Integer、Long、Double等)

StringToCharacterConverter

String----->java.lang.Character

取字符串第一个字符

NumberToCharacterConverter

Number子类型(Integer、Long、Double等)——> java.lang.Character

CharacterToNumberFactory

java.lang.Character ——>Number子类型(Integer、Long、Double等)

StringToEnumConverterFactory

String----->enum类型

通过Enum.valueOf将字符串转换为需要的enum类型

EnumToStringConverter

enum类型----->String

返回enum对象的name()值

StringToLocaleConverter

String----->java.util.Local

PropertiesToStringConverter

java.util.Properties----->String

默认通过ISO-8859-1解码

StringToPropertiesConverter

String----->java.util.Properties

默认使用ISO-8859-1编码

第二组:集合、数组相关转换器

ArrayToCollectionConverter

任意S数组---->任意T集合(List、Set)

CollectionToArrayConverter

任意T集合(List、Set)---->任意S数组

ArrayToArrayConverter

任意S数组<---->任意T数组

CollectionToCollectionConverter

任意T集合(List、Set)<---->任意T集合(List、Set)

即集合之间的类型转换

MapToMapConverter

Map<---->Map之间的转换

ArrayToStringConverter

任意S数组---->String类型

StringToArrayConverter

String----->数组

默认通过“,”分割,且去除字符串的两边空格(trim)

ArrayToObjectConverter

任意S数组---->任意Object的转换

(如果目标类型和源类型兼容,直接返回源对象;否则返回S数组的第一个元素并进行类型转换)

ObjectToArrayConverter

Object----->单元素数组

CollectionToStringConverter

任意T集合(List、Set)---->String类型

StringToCollectionConverter

String----->集合(List、Set)

默认通过“,”分割,且去除字符串的两边空格(trim)

CollectionToObjectConverter

任意T集合---->任意Object的转换

(如果目标类型和源类型兼容,直接返回源对象;否则返回S数组的第一个元素并进行类型转换)

ObjectToCollectionConverter

Object----->单元素集合

第三组:默认(fallback)转换器:之前的转换器不能转换时调用

ObjectToObjectConverter

Object(S)----->Object(T)

首先尝试valueOf进行转换、没有则尝试new 构造器(S)

IdToEntityConverter

Id(S)----->Entity(T)

查找并调用public static T find[EntityName](S)获取目标对象,EntityName是T类型的简单类型

FallbackObjectToStringConverter

Object----->String

ConversionService作为恢复使用,即其他转换器不能转换时调用(执行对象的toString()方法)

S:代表源类型,T:代表目标类型

如上的转换器在使用转换服务实现DefaultConversionService和DefaultFormattingConversionService时会自动注册。

7.2.2.3、示例

(1、自定义String----->PhoneNumberModel的转换器

Java代码  
  1. package cn.javass.chapter7.web.controller.support.converter;   
  2. //省略import   
  3. public class StringToPhoneNumberConverter implements Converter<String, PhoneNumberModel> {   
  4.     Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");   
  5.     @Override  
  6.     public PhoneNumberModel convert(String source) {           
  7.         if(!StringUtils.hasLength(source)) {   
  8.             //①如果source为空 返回null   
  9.             return null;   
  10.         }   
  11.         Matcher matcher = pattern.matcher(source);   
  12.         if(matcher.matches()) {   
  13.             //②如果匹配 进行转换   
  14.             PhoneNumberModel phoneNumber = new PhoneNumberModel();   
  15.             phoneNumber.setAreaCode(matcher.group(1));   
  16.             phoneNumber.setPhoneNumber(matcher.group(2));   
  17.             return phoneNumber;   
  18.         } else {   
  19.             //③如果不匹配 转换失败   
  20.             throw new IllegalArgumentException(String.format("类型转换失败,需要格式[010-12345678],但格式是[%s]", source));   
  21.         }   
  22.     }   
  23. }  

String转换为Date的类型转换器,请参考cn.javass.chapter7.web.controller.support.converter.StringToDateConverter。

(2、测试用例(cn.javass.chapter7.web.controller.support.converter.ConverterTest)

Java代码
  1. @Test  
  2. public void testStringToPhoneNumberConvert() {   
  3.     DefaultConversionService conversionService = new DefaultConversionService();   
  4.     conversionService.addConverter(new StringToPhoneNumberConverter());   
  5.        
  6.     String phoneNumberStr = "010-12345678";   
  7.     PhoneNumberModel phoneNumber = conversionService.convert(phoneNumberStr, PhoneNumberModel.class);   
  8.            
  9.     Assert.assertEquals("010", phoneNumber.getAreaCode());   
  10. }  

 类似于PhoneNumberEditor将字符串“010-12345678”转换为PhoneNumberModel。

Java代码
  1. @Test  
  2. public void testOtherConvert() {   
  3.     DefaultConversionService conversionService = new DefaultConversionService();   
  4.        
  5.     //"1"--->true(字符串“1”可以转换为布尔值true)   
  6.     Assert.assertEquals(Boolean.valueOf(true), conversionService.convert("1", Boolean.class));   
  7.        
  8.     //"1,2,3,4"--->List(转换完毕的集合大小为4)   
  9.     Assert.assertEquals(4, conversionService.convert("1,2,3,4", List.class).size());   
  10. }

 其他类型转换器使用也是类似的,此处不再重复。

7.2.2.4、集成到Spring Web MVC环境

(1、注册ConversionService实现和自定义的类型转换器

Java代码
  1. <!-- ①注册ConversionService -->   
  2. <bean id="conversionService" class="org.springframework.format.support.   
  3.                                              FormattingConversionServiceFactoryBean">   
  4.     <property name="converters">   
  5.        <list>   
  6.             <bean class="cn.javass.chapter7.web.controller.support.   
  7.                              converter.StringToPhoneNumberConverter"/>   
  8.             <bean class="cn.javass.chapter7.web.controller.support.   
  9.                              converter.StringToDateConverter">   
  10.                 <constructor-arg value="yyyy-MM-dd"/>   
  11.             </bean>   
  12.         </list>   
  13.     </property>   
  14. </bean>

 FormattingConversionServiceFactoryBean:是FactoryBean实现,默认使用DefaultFormattingConversionService转换器服务实现;

converters:注册我们自定义的类型转换器,此处注册了String--->PhoneNumberModel和String--->Date的类型转换器。

(2、通过ConfigurableWebBindingInitializer注册ConversionService

Java代码
  1. <!-- ②使用ConfigurableWebBindingInitializer注册conversionService -->   
  2. <bean id="webBindingInitializer" class="org.springframework.web.bind.support.   
  3.                                                                         ConfigurableWebBindingInitializer">   
  4.     <property name="conversionService" ref="conversionService"/>   
  5. </bean> 

 此处我们通过ConfigurableWebBindingInitializer绑定初始化器进行ConversionService的注册;

3、注册ConfigurableWebBindingInitializer到RequestMappingHandlerAdapter

Java代码
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"><property name="webBindingInitializer" ref="webBindingInitializer"/></bean>

 通过如上配置,我们就完成了Spring3.0的类型转换系统与Spring Web MVC的集成。此时可以启动服务器输入之前的URL测试了。

此时可能有人会问,如果我同时使用PropertyEditor和ConversionService,执行顺序是什么呢?内部首先查找PropertyEditor进行类型转换,如果没有找到相应的PropertyEditor再通过ConversionService进行转换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值