Spring3 Web MVC下的数据类型转换

基于spring-framework-3.1.1.RELEASE

7.1、简介

在编写可视化界面项目时,我们通常需要对数据进行类型转换、验证及格式化。

 

一、在Spring3之前,我们使用如下架构进行类型转换、验证及格式化:


 

流程:

①:类型转换:首先调用PropertyEditor的setAsText(String),内部根据需要调用setValue(Object)方法进行设置转换后的值;

②:数据验证:需要显示调用Spring的Validator接口实现进行数据验证;

③:格式化显示:需要调用PropertyEditor的getText进行格式化显示。

 

使用如上架构的缺点是:

1、PropertyEditor被设计为只能String<——>Object之间转换,不能任意对象类型<——>任意类型,如我们常见的Long时间戳到Date类型的转换是办不到的;

2、PropertyEditor是线程不安全的,也就是有状态的,因此每次使用时都需要创建一个,不可重用;

3、PropertyEditor不是强类型的,setValue(Object)可以接受任意类型,因此需要我们自己判断类型是否兼容;

4、需要自己编程实现验证,Spring3支持更棒的注解验证支持;

5、在使用SpEL表达式语言或DataBinder时,只能进行String<--->Object之间的类型转换;

6不支持细粒度的类型转换/格式化,如UserModel的registerDate需要转换/格式化类似“2012-05-01”的数据,而OrderModel的orderDate需要转换/格式化类似“2012-05-01 15:11:13”的数据,因为大家都为java.util.Date类型,因此不太容易进行细粒度转换/格式化。

 

Spring Web MVC环境中,数据类型转换、验证及格式化通常是这样使用的:

Spring3 Web MVC下的数据类型转换_第1张图片

流程:

①、类型转换:首先表单数据(全部是字符串)通过WebDataBinder进行绑定到命令对象,内部通过PropertyEditor实现;

②:数据验证:在控制器中的功能处理方法中,需要显示的调用Spring的Validator实现并将错误信息添加到BindingResult对象中;

③:格式化显示:在表单页面可以通过如下方式展示通过PropertyEditor格式化的数据和错误信息:

 

Java代码 复制代码  收藏代码
  1. <%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>   
  2. <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>  
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

 首先需要通过如上taglib指令引入spring的两个标签库。

 

Java代码 复制代码  收藏代码
  1. //1、格式化单个命令/表单对象的值(好像比较麻烦,真心没有好办法)   
  2. <spring:bind path="dataBinderTest.phoneNumber">${status.value}</spring:bind>  
//1、格式化单个命令/表单对象的值(好像比较麻烦,真心没有好办法)
<spring:bind path="dataBinderTest.phoneNumber">${status.value}</spring:bind>
 
Java代码 复制代码  收藏代码
  1. //2、<spring:eval>标签,自动调用ConversionService并选择相应的Converter SPI进行格式化展示   
  2. <spring:eval expression="dataBinderTest.phoneNumber"></spring:eval>  
//2、<spring:eval>标签,自动调用ConversionService并选择相应的Converter SPI进行格式化展示
<spring:eval expression="dataBinderTest.phoneNumber"></spring:eval>

 如上代码能工作的前提是在RequestMappingHandlerMapping配置了ConversionServiceExposingInterceptor,它的作用是暴露conversionService到请求中以便如<spring:eval>标签使用。

 

Java代码 复制代码  收藏代码
  1. //3、通过form标签,内部的表单标签会自动调用命令/表单对象属性对应的PropertyEditor进行格式化显示   
  2. <form:form commandName="dataBinderTest">   
  3.     <form:input path="phoneNumber"/><!-- 如果出错会显示错误之前的数据而不是空 -->   
  4. </form:form>  
//3、通过form标签,内部的表单标签会自动调用命令/表单对象属性对应的PropertyEditor进行格式化显示
<form:form commandName="dataBinderTest">
    <form:input path="phoneNumber"/><!-- 如果出错会显示错误之前的数据而不是空 -->
</form:form>
 
Java代码 复制代码  收藏代码
  1. //4、显示验证失败后的错误信息   
  2. <form:errors></form:errors>  
//4、显示验证失败后的错误信息
<form:errors></form:errors>

 接下来我们就详细学习一下这些知识吧。

 

 

7.2、数据类型转换

7.2.1、Spring3之前的PropertyEditor

PropertyEditor介绍请参考【4.16.1、数据类型转换】。

 

一、测试之前我们需要准备好测试环境:

1、模型对象,和【4.16.1、数据类型转换】使用的一样,需要将DataBinderTestModel模型类及相关类拷贝过来放入cn.javass.chapter7.model包

2、控制器定义:

Java代码 复制代码  收藏代码
  1. package cn.javass.chapter7.web.controller;   
  2. //省略import   
  3. @Controller  
  4. public class DataBinderTestController {   
  5.     @RequestMapping(value = "/dataBind")   
  6.     public String test(DataBinderTestModel command) {   
  7.             //输出command对象看看是否绑定正确   
  8.         System.out.println(command);   
  9.         model.addAttribute("dataBinderTest", command);   
  10.         return "bind/success";   
  11.     }   
  12. }  
package cn.javass.chapter7.web.controller;
//省略import
@Controller
public class DataBinderTestController {
    @RequestMapping(value = "/dataBind")
    public String test(DataBinderTestModel command) {
        	//输出command对象看看是否绑定正确
		System.out.println(command);
		model.addAttribute("dataBinderTest", command);
		return "bind/success";
	}
}

 

 (3、Spring配置文件定义,请参考chapter7-servlet.xml,并注册DataBinderTestController

Java代码 复制代码  收藏代码
  1. <bean class="cn.javass.chapter7.web.controller.DataBinderTestController"/>   
  2.       
<bean class="cn.javass.chapter7.web.controller.DataBinderTestController"/>
    

 4、测试的URL:

 

http://localhost:9080/springmvc-chapter7/dataBind?username=zhang&bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&phoneNumber=010-12345678&date=2012-3-18 16:48:48&state=blocked

 

 

二、注解式控制器注册PropertyEditor:

1、使用WebDataBinder进行控制器级别注册PropertyEditor(控制器独享)

Java代码 复制代码  收藏代码
  1. @InitBinder  
  2. //此处的参数也可以是ServletRequestDataBinder类型   
  3. public void initBinder(WebDataBinder binder) throws Exception {   
  4.     //注册自定义的属性编辑器   
  5.     //1、日期   
  6.     DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");   
  7.     CustomDateEditor dateEditor = new CustomDateEditor(df, true);   
  8.     //表示如果命令对象有Date类型的属性,将使用该属性编辑器进行类型转换   
  9.     binder.registerCustomEditor(Date.class, dateEditor);       
  10.     //自定义的电话号码编辑器(和【4.16.1、数据类型转换】一样)   
  11.     binder.registerCustomEditor(PhoneNumberModel.classnew PhoneNumberEditor());   
  12. }  
@InitBinder
//此处的参数也可以是ServletRequestDataBinder类型
public void initBinder(WebDataBinder binder) throws Exception {
	//注册自定义的属性编辑器
	//1、日期
	DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	CustomDateEditor dateEditor = new CustomDateEditor(df, true);
	//表示如果命令对象有Date类型的属性,将使用该属性编辑器进行类型转换
	binder.registerCustomEditor(Date.class, dateEditor);	
	//自定义的电话号码编辑器(和【4.16.1、数据类型转换】一样)
	binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());
}

 和【4.16.1、数据类型转换】一节类似,只是此处需要通过@InitBinder来注册自定义的PropertyEditor。

 

2、使用WebBindingInitializer批量注册PropertyEditor

4.16.1、数据类型转换】不太一样,因为我们的注解式控制器是POJO,没有实现任何东西,因此无法注入WebBindingInitializer,此时我们需要把WebBindingInitializer注入到我们的RequestMappingHandlerAdapter或AnnotationMethodHandlerAdapter,这样对于所有的注解式控制器都是共享的。

Java代码 复制代码  收藏代码
  1. <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">   
  2.     <property name="webBindingInitializer">   
  3.         <bean class="cn.javass.chapter7.web.controller.support.initializer.MyWebBindingInitializer"/>   
  4.     </property>   
  5. </bean>  
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="webBindingInitializer">
        <bean class="cn.javass.chapter7.web.controller.support.initializer.MyWebBindingInitializer"/>
    </property>
</bean>

 

 此时我们注释掉控制器级别通过@InitBinder注册PropertyEditor的方法。

 

3、全局级别注册PropertyEditor(全局共享)

和【4.16.1、数据类型转换】一节一样,此处不再重复。请参考【4.16.1、数据类型转换】的【全局级别注册PropertyEditor(全局共享)】。

 

接下来我们看一下Spring3提供的更强大的类型转换支持。

7.2.2、Spring3开始的类型转换系统

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等。

7.2.2.1、架构

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

Spring3 Web MVC下的数据类型转换_第2张图片

 

一个有如下三种接口:

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

Java代码 复制代码  收藏代码
  1. package org.springframework.core.convert.converter;   
  2. public interface Converter<S, T> { //① S是源类型 T是目标类型   
  3.     T convert(S source); //② 转换S类型的source到T目标类型的转换方法   
  4. }  
package org.springframework.core.convert.converter;
public interface Converter<S, T> { //① S是源类型 T是目标类型
	T convert(S source); //② 转换S类型的source到T目标类型的转换方法
}

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

 

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

 

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

Java代码 复制代码  收藏代码
  1. package org.springframework.core.convert.converter;   
  2. public interface GenericConverter {   
  3.     Set<ConvertiblePair> getConvertibleTypes();   
  4.     Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);   
  5. }  
package org.springframework.core.convert.converter;
public interface GenericConverter {
	Set<ConvertiblePair> getConvertibleTypes();
	Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

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

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

Java代码 复制代码  收藏代码
  1. package org.springframework.core.convert.converter;   
  2. public interface ConditionalGenericConverter extends GenericConverter {   
  3.     boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);   
  4. }  
package org.springframework.core.convert.converter;
public interface ConditionalGenericConverter extends GenericConverter {
	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代码 复制代码  收藏代码
  1. package org.springframework.core.convert.converter;   
  2. public interface ConverterFactory<S, R> {   
  3.     <T extends R> Converter<S, T> getConverter(Class<T> targetType);   
  4. }  
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
	<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支持。

Spring3 Web MVC下的数据类型转换_第3张图片
 一共有如下两种接口:

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

Java代码 复制代码  收藏代码
  1. package org.springframework.core.convert.converter;   
  2. public interface ConverterRegistry {   
  3.     void addConverter(Converter<?, ?> converter);   
  4.     void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);   
  5.     void addConverter(GenericConverter converter);   
  6.     void addConverterFactory(ConverterFactory<?, ?> converterFactory);   
  7.     void removeConvertible(Class<?> sourceType, Class<?> targetType);   
  8. }  
package org.springframework.core.convert.converter;
public interface ConverterRegistry {
	void addConverter(Converter<?, ?> converter);
	void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);
	void addConverter(GenericConverter converter);
	void addConverterFactory(ConverterFactory<?, ?> converterFactory);
	void removeConvertible(Class<?> sourceType, Class<?> targetType);
}

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

 

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

Java代码 复制代码  收藏代码
  1. package org.springframework.core.convert;   
  2. public interface ConversionService {   
  3.     boolean canConvert(Class<?> sourceType, Class<?> targetType);   
  4.     boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);   
  5.     <T> T convert(Object source, Class<T> targetType);   
  6.     Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);   
  7. }  
package org.springframework.core.convert;
public interface ConversionService {
	boolean canConvert(Class<?> sourceType, Class<?> targetType);
	boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
	<T> T convert(Object source, Class<T> targetType);
	Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

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

 

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

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

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

 

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

类名

说明

第一组:标量转换器

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

 

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. }  
@Test
public void testStringToPhoneNumberConvert() {
	DefaultConversionService conversionService = new DefaultConversionService();
	conversionService.addConverter(new StringToPhoneNumberConverter());
	
	String phoneNumberStr = "010-12345678";
	PhoneNumberModel phoneNumber = conversionService.convert(phoneNumberStr, PhoneNumberModel.class);
		
	Assert.assertEquals("010", phoneNumber.getAreaCode());
}

 类似于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. }  
@Test
public void testOtherConvert() {
	DefaultConversionService conversionService = new DefaultConversionService();
	
	//"1"--->true(字符串“1”可以转换为布尔值true)
	Assert.assertEquals(Boolean.valueOf(true), conversionService.convert("1", Boolean.class));
	
	//"1,2,3,4"--->List(转换完毕的集合大小为4)
	Assert.assertEquals(4, conversionService.convert("1,2,3,4", List.class).size());
}

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

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>  
<!-- ①注册ConversionService -->
<bean id="conversionService" class="org.springframework.format.support.
	 	 	 	 	 	 	 	 	 	 	 FormattingConversionServiceFactoryBean">
    <property name="converters">
       <list>
	        <bean class="cn.javass.chapter7.web.controller.support.
	 	 	 	 	 	 	 converter.StringToPhoneNumberConverter"/>
	        <bean class="cn.javass.chapter7.web.controller.support.
	 	 	 	 	 	 	 converter.StringToDateConverter">
	            <constructor-arg value="yyyy-MM-dd"/>
	        </bean>
        </list>
    </property>
</bean>

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

converters:注册我们自定义的类型转换器,此处注册了String--->PhoneNumberModelString--->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 -->
<bean id="webBindingInitializer" class="org.springframework.web.bind.support.
	        	        	        	        	        	        ConfigurableWebBindingInitializer">
	<property name="conversionService" ref="conversionService"/>
</bean>

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

 

3、注册ConfigurableWebBindingInitializer到RequestMappingHandlerAdapter

Java代码 复制代码  收藏代码
  1. <bean class="org.springframework.web.servlet.mvc.method.annotation.   
  2.                                                             RequestMappingHandlerAdapter">   
  3. <property name="webBindingInitializer" ref="webBindingInitializer"/>   
  4. </bean>  
<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进行转换。

 

如上集成过程看起来比较麻烦,后边我们会介绍<mvc:annotation-driven>和@EnableWebMvc,ConversionService会自动注册,后续章节再详细介绍。

 

  • 大小: 11.6 KB
  • Spring3 Web MVC下的数据类型转换_第4张图片
  • 大小: 20.6 KB
  • 大小: 17.3 KB
  • 大小: 23.9 KB
  • 大小: 19.7 KB
  • 大小: 36.1 KB
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
 
   发表时间:2012-05-02   最后修改:2012-05-02

补充 PropertyEditor进行类型转换。

 

4.16.1、数据类型转换

请求参数(String)——>命令对象属性(可能是任意类型)的类型转换,即数据绑定时的类型转换,使用PropertyEditor实现绑定时的类型转换。

 

一、Spring内建的PropertyEditor如下所示:

  

类名

说明

默认是否注册

ByteArrayPropertyEditor

String<——>byte[]

ClassEditor

String<——>Class

当类没有发现抛出IllegalArgumentException

CustomBooleanEditor

String<——>Boolean

true/yes/on/1转换为true,false/no/off/0转换为false

CustomCollectionEditor

数组/Collection——>Collection

普通值——>Collection(只包含一个对象)

String——>Collection

不允许Collection——>String(单方向转换)

CustomNumberEditor

String<——>Number(Integer、Long、Double)

FileEditor

String<——>File

InputStreamEditor

String——>InputStream

单向的,不能InputStream——>String

LocaleEditor

String<——>Locale,

String的形式为[语言]_[国家]_[变量],这与Local对象的toString()方法得到的结果相同)

PatternEditor

String<——>Pattern

PropertiesEditor

String<——>java.lang.Properties

URLEditor

String<——>URL

StringTrimmerEditor

一个用于trim 的 String类型的属性编辑器

如默认删除两边的空格,charsToDelete属性:可以设置为其他字符

emptyAsNull属性:将一个空字符串转化为null值的选项。

×

CustomDateEditor

String<——>java.util.Date

×

 

二、Spring内建的PropertyEditor支持的属性(符合JavaBean规范)操作:

表达式

设值/取值说明

username

属性username

设值方法setUsername()/取值方法getUsername() 或 isUsername()

schooInfo.schoolType

属性schooInfo的嵌套属性schoolType

设值方法getSchooInfo().setSchoolType()/取值方法getSchooInfo().getSchoolType()

hobbyList[0]

属性hobbyList的第一个元素

索引属性可能是一个数组、列表、其它天然有序的容器。

map[key]

属性map(java.util.Map类型)

map中key对应的值

 

 

三、示例:

接下来我们写自定义的属性编辑器进行数据绑定:

1、模型对象:

Java代码 复制代码  收藏代码
  1. package cn.javass.chapter4.model;   
  2. //省略import   
  3. public class DataBinderTestModel {   
  4.     private String username;   
  5.     private boolean bool;//Boolean值测试   
  6.     private SchoolInfoModel schooInfo;   
  7.     private List hobbyList;//集合测试,此处可以改为数组/Set进行测试   
  8.     private Map map;//Map测试   
  9.     private PhoneNumberModel phoneNumber;//String->自定义对象的转换测试   
  10.     private Date date;//日期类型测试   
  11.     private UserState state;//String——>Enum类型转换测试   
  12.     //省略getter/setter   
  13. }   
  14.   
  15. package cn.javass.chapter4.model;   
  16. //如格式010-12345678   
  17. public class PhoneNumberModel {   
  18.     private String areaCode;//区号   
  19.     private String phoneNumber;//电话号码   
  20.     //省略getter/setter   
  21. }  
package cn.javass.chapter4.model;
//省略import
public class DataBinderTestModel {
	private String username;
	private boolean bool;//Boolean值测试
	private SchoolInfoModel schooInfo;
	private List hobbyList;//集合测试,此处可以改为数组/Set进行测试
	private Map map;//Map测试
	private PhoneNumberModel phoneNumber;//String->自定义对象的转换测试
	private Date date;//日期类型测试
	private UserState state;//String——>Enum类型转换测试
    //省略getter/setter
}

package cn.javass.chapter4.model;
//如格式010-12345678
public class PhoneNumberModel {
	private String areaCode;//区号
	private String phoneNumber;//电话号码
    //省略getter/setter
}

 

(2PhoneNumber属性编辑器

前台输入如010-12345678自动转换为PhoneNumberModel

Java代码 复制代码  收藏代码
  1. package cn.javass.chapter4.web.controller.support.editor;   
  2. //省略import   
  3. public class PhoneNumberEditor extends PropertyEditorSupport {   
  4.     Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");   
  5.     @Override  
  6.     public void setAsText(String text) throws IllegalArgumentException {   
  7.         if(text == null || !StringUtils.hasLength(text)) {   
  8.             setValue(null); //如果没值,设值为null   
  9.         }   
  10.         Matcher matcher = pattern.matcher(text);   
  11.         if(matcher.matches()) {   
  12.             PhoneNumberModel phoneNumber = new PhoneNumberModel();   
  13.             phoneNumber.setAreaCode(matcher.group(1));   
  14.             phoneNumber.setPhoneNumber(matcher.group(2));   
  15.             setValue(phoneNumber);   
  16.         } else {   
  17.             throw new IllegalArgumentException(String.format("类型转换失败,需要格式[010-12345678],但格式是[%s]", text));   
  18.         }   
  19.     }   
  20.     @Override  
  21.     public String getAsText() {   
  22.         PhoneNumberModel phoneNumber = ((PhoneNumberModel)getValue());   
  23.         return phoneNumber == null ? "" : phoneNumber.getAreaCode() + "-" + phoneNumber.getPhoneNumber();   
  24.     }   
  25. }  
package cn.javass.chapter4.web.controller.support.editor;
//省略import
public class PhoneNumberEditor extends PropertyEditorSupport {
	Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");
	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		if(text == null || !StringUtils.hasLength(text)) {
			setValue(null); //如果没值,设值为null
		}
		Matcher matcher = pattern.matcher(text);
		if(matcher.matches()) {
			PhoneNumberModel phoneNumber = new PhoneNumberModel();
			phoneNumber.setAreaCode(matcher.group(1));
			phoneNumber.setPhoneNumber(matcher.group(2));
			setValue(phoneNumber);
		} else {
			throw new IllegalArgumentException(String.format("类型转换失败,需要格式[010-12345678],但格式是[%s]", text));
		}
	}
	@Override
	public String getAsText() {
		PhoneNumberModel phoneNumber = ((PhoneNumberModel)getValue());
		return phoneNumber == null ? "" : phoneNumber.getAreaCode() + "-" + phoneNumber.getPhoneNumber();
	}
}

PropertyEditorSupport一个PropertyEditor的支持类;

setAsText表示将String——>PhoneNumberModel,根据正则表达式进行转换,如果转换失败抛出异常,则接下来的验证器会进行验证处理;

getAsText表示将PhoneNumberModel——>String

 

3、控制器

需要在控制器注册我们自定义的属性编辑器。

此处我们使用AbstractCommandController,因为它继承了BaseCommandController,拥有绑定流程。

 

Java代码 复制代码  收藏代码
  1. package cn.javass.chapter4.web.controller;   
  2. //省略import   
  3. public class DataBinderTestController extends AbstractCommandController {   
  4.     public DataBinderTestController() {   
  5.         setCommandClass(DataBinderTestModel.class); //设置命令对象   
  6.         setCommandName("dataBinderTest");//设置命令对象的名字   
  7.     }   
  8.     @Override  
  9.     protected ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {   
  10.         //输出command对象看看是否绑定正确   
  11.         System.out.println(command);   
  12.         return new ModelAndView("bindAndValidate/success").addObject("dataBinderTest", command);   
  13.     }   
  14.     @Override  
  15.     protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {   
  16.         super.initBinder(request, binder);   
  17.         //注册自定义的属性编辑器   
  18.         //1、日期   
  19.         DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");   
  20.         CustomDateEditor dateEditor = new CustomDateEditor(df, true);   
  21.         //表示如果命令对象有Date类型的属性,将使用该属性编辑器进行类型转换   
  22.         binder.registerCustomEditor(Date.class, dateEditor);   
  23.         //自定义的电话号码编辑器   
  24.         binder.registerCustomEditor(PhoneNumberModel.classnew PhoneNumberEditor());   
  25.     }   
  26. }  
package cn.javass.chapter4.web.controller;
//省略import
public class DataBinderTestController extends AbstractCommandController {
	public DataBinderTestController() {
		setCommandClass(DataBinderTestModel.class); //设置命令对象
		setCommandName("dataBinderTest");//设置命令对象的名字
	}
	@Override
	protected ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {
		//输出command对象看看是否绑定正确
		System.out.println(command);
		return new ModelAndView("bindAndValidate/success").addObject("dataBinderTest", command);
	}
	@Override
	protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
		super.initBinder(request, binder);
		//注册自定义的属性编辑器
		//1、日期
		DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		CustomDateEditor dateEditor = new CustomDateEditor(df, true);
		//表示如果命令对象有Date类型的属性,将使用该属性编辑器进行类型转换
		binder.registerCustomEditor(Date.class, dateEditor);
		//自定义的电话号码编辑器
		binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());
	}
}

initBinder:第一个扩展点,初始化数据绑定器,在此处我们注册了两个属性编辑器;

CustomDateEditor自定义的日期编辑器,用于在String<——>日期之间转换;

    binder.registerCustomEditor(Date.class, dateEditor):表示如果命令对象是Date类型,则使用dateEditor进行类型转换;

PhoneNumberEditor自定义的电话号码属性编辑器用于在String<——> PhoneNumberModel之间转换;

    binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor()):表示如果命令对象是PhoneNumberModel类型,则使用PhoneNumberEditor进行类型转换;

4、spring配置文件chapter4-servlet.xml

Java代码 复制代码  收藏代码
  1. <bean name="/dataBind"    
  2. class="cn.javass.chapter4.web.controller.DataBinderTestController"/>  
<bean name="/dataBind" 
class="cn.javass.chapter4.web.controller.DataBinderTestController"/>

 5、视图页面(WEB-INF/jsp/bindAndValidate/success.jsp

Java代码 复制代码  收藏代码
  1. EL phoneNumber:${dataBinderTest.phoneNumber}<br/>   
  2. EL state:${dataBinderTest.state}<br/>   
  3. EL date:${dataBinderTest.date}<br/>  
EL phoneNumber:${dataBinderTest.phoneNumber}<br/>
EL state:${dataBinderTest.state}<br/>
EL date:${dataBinderTest.date}<br/>

 视图页面的数据没有预期被格式化,如何进行格式化显示呢?请参考【第七章  注解式控制器的数据验证、类型转换及格式化】。

 

6、测试:

1、在浏览器地址栏输入请求的URL,如

http://localhost:9080/springmvc-chapter4/dataBind?username=zhang&bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&phoneNumber=010-12345678&date=2012-3-18 16:48:48&state=blocked

 

2、控制器输出的内容:

DataBinderTestModel [username=zhang, bool=true, schooInfo=SchoolInfoModel [schoolType=null, schoolName=null, specialty=computer], hobbyList=[program, music], map={key1=value1, key2=value2}, phoneNumber=PhoneNumberModel [areaCode=010, phoneNumber=12345678], date=Sun Mar 18 16:48:48 CST 2012, state=锁定]

类型转换如图所示:


Spring3 Web MVC下的数据类型转换_第5张图片

 

四、注册PropertyEditor

1、使用WebDataBinder进行控制器级别注册PropertyEditor(控制器独享)

如“【三、示例】”中所使用的方式,使用WebDataBinder注册控制器级别的PropertyEditor,这种方式注册的PropertyEditor只对当前控制器独享,即其他的控制器不会自动注册这个PropertyEditor,如果需要还需要再注册一下。

 

2、使用WebBindingInitializer批量注册PropertyEditor

如果想在多个控制器同时注册多个相同的PropertyEditor时,可以考虑使用WebBindingInitializer

 

示例:

1、实现WebBindingInitializer

Java代码 复制代码  收藏代码
  1. package cn.javass.chapter4.web.controller.support.initializer;   
  2. //省略import   
  3. public class MyWebBindingInitializer implements WebBindingInitializer {   
  4.     @Override  
  5.     public void initBinder(WebDataBinder binder, WebRequest request) {   
  6.         //注册自定义的属性编辑器   
  7.         //1、日期   
  8.         DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");   
  9.         CustomDateEditor dateEditor = new CustomDateEditor(df, true);   
  10.         //表示如果命令对象有Date类型的属性,将使用该属性编辑器进行类型转换   
  11.         binder.registerCustomEditor(Date.class, dateEditor);   
  12.         //自定义的电话号码编辑器   
  13.         binder.registerCustomEditor(PhoneNumberModel.classnew PhoneNumberEditor());   
  14.     }   
  15. }  
package cn.javass.chapter4.web.controller.support.initializer;
//省略import
public class MyWebBindingInitializer implements WebBindingInitializer {
	@Override
	public void initBinder(WebDataBinder binder, WebRequest request) {
		//注册自定义的属性编辑器
		//1、日期
		DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		CustomDateEditor dateEditor = new CustomDateEditor(df, true);
		//表示如果命令对象有Date类型的属性,将使用该属性编辑器进行类型转换
		binder.registerCustomEditor(Date.class, dateEditor);
		//自定义的电话号码编辑器
		binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());
	}
}

 通过实现WebBindingInitializer并通过binder注册多个PropertyEditor

 

2、修改【三、示例】中的DataBinderTestController,注释掉initBinder方法;

 

3、修改chapter4-servlet.xml配置文件:

Java代码 复制代码  收藏代码
  1. <!-- 注册WebBindingInitializer实现 -->   
  2. <bean id="myWebBindingInitializer" class="cn.javass.chapter4.web.controller.support.initializer.MyWebBindingInitializer"/>   
  3. <bean name="/dataBind" class="cn.javass.chapter4.web.controller.DataBinderTestController">   
  4.     <!-- 注入WebBindingInitializer实现 -->   
  5.     <property name="webBindingInitializer" ref="myWebBindingInitializer"/>   
  6. </bean>  
<!-- 注册WebBindingInitializer实现 -->
<bean id="myWebBindingInitializer" class="cn.javass.chapter4.web.controller.support.initializer.MyWebBindingInitializer"/>
<bean name="/dataBind" class="cn.javass.chapter4.web.controller.DataBinderTestController">
    <!-- 注入WebBindingInitializer实现 -->
    <property name="webBindingInitializer" ref="myWebBindingInitializer"/>
</bean>

 4、尝试访问“【三、示例】”中的测试URL即可成功。

 

使用WebBindingInitializer的好处是当你需要在多个控制器中需要同时使用多个相同的PropertyEditor可以在WebBindingInitializer实现中注册,这样只需要在控制器中注入WebBindingInitializer即可注入多个PropertyEditor

 

3、全局级别注册PropertyEditor(全局共享)

只需要将我们自定义的PropertyEditor放在和你的模型类同包下即可,且你的Editor命名规则必须是“模型类名Editor”,这样Spring会自动使用标准JavaBean架构进行自动识别,如图所示:


Spring3 Web MVC下的数据类型转换_第6张图片

 

 

此时我们把“DataBinderTestController”的“binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());”注释掉,再尝试访问“【三、示例】”中的测试URL即可成功。

 

这种方式不仅仅在使用Spring时可用,在标准的JavaBean等环境都是可用的,可以认为是全局共享的(不仅仅是Spring环境)。

 

PropertyEditor被限制为只能String<——>Object之间转换,不能Object<——>ObjectSpring3提供了更强大的类型转换(Type Conversion)支持,它可以在任意对象之间进行类型转换,不仅仅是String<——>Object。

 

如果我在地址栏输入错误的数据,即数据绑定失败,Spring Web MVC该如何处理呢?如果我输入的数据不合法呢?如用户名输入100个字符(超长了)那又该怎么处理呢?出错了需要错误消息,那错误消息应该是硬编码?还是可配置呢?

你可能感兴趣的:(Spring3)