===================基于注解方式的控制器的数据验证,类型转换和格式化=========
-----------------spring3之前
springMVC数据类型转换,验证及格式化的流程是:
a 类型转换: 表单数据通过webDataBinder绑定到命令对象(内部通过propertyEditor实现)
b 数据验证:在处理方法中,显示的调用spring的validator,并将错误信息添加到
bindingResult对象中
c 格式化显示:在表单页可以通过如下方式显示propertyEditor和错误信息
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
//格式化单个命令
<spring:bind path="dataBinderTest.phoneNumber">${status.value}</spring:bind>
//通过form标签自动调用命令
<form:form commandName="dataBinderTest">
<form:input path="phoneNumber"/>
<!-- 如果出错会显示错误之前的数据 -->
</form:form>
//显示错误信息
<form:errors></form:errors>
------------------spring3开始:
类型转换:conversionService会自动选择相应的converter spi进行转换
数据验证: 支持jsp-303 验证框架,只需将@valid放到目标类型上即可
格式化显示: converterSPI完成任意类型到string的转换
springMVC数据类型转换,验证及格式化的流程是
类型转换:表单提交数据,webDataBinder进行数据绑定到命令对象(通过converter spi)
数据验证:使用jsp-303验证框架进行
格式化显示:通过以下方式显示数据和错误信息:
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
//格式化单个命令
<spring:bind path="dataBinderTest.phoneNumber">${status.value}</spring:bind>
//<spring:eval>标签,自动调用ConversionService并选择相应的Converter SPI进行格式化展示
<spring:eval expression="dataBinderTest.phoneNumber"></spring:eval>
//通过form标签自动调用命令
<form:form commandName="dataBinderTest">
<form:input path="phoneNumber"/>
<!-- 如果出错会显示错误之前的数据 -->
</form:form>
//显示错误信息
<form:errors></form:errors>
--------------------------spring3开始的类型转换系统
类型转换器有如下三种接口:
1 converter:转换s类型到t类型,实现此接口必须是线程安全且可以被共享
接口原型:
public interface Converter<S, T> {
T convert(S source);
}
2 genericConverter/conditionalGenericConverter:
genericConverter实现此接口能在多种类型之间转换
conditionalGenericConverter有条件的在多种类型之间转换
接口原型:
public interface GenericConverter {
//指定可转换的目标类型
Set<ConvertiblePair> getConvertibleTypes();
//在sourceType和targetType之间转换
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
3 converterFactory:用于选择将一种s源类型转换为r类型的子类型t的转换器工厂
接口原型:
public interface ConverterFactory<S, R> {
//r:目标类型 t:目标类型是r的子类型
//得到目标类型的对应转换器
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
.......................类型转换器的注册和使用
有两类接口:
1 ConverterRegistry: 注册转换器接口
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 : 类型转换服务接口
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
//将源对象转换为目标对象
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
默认实现
DefaultConversionService:默认的类型转换服务实现
DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可
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----->StringConversionService 作为恢复使用,即其他转换器不能转换时调用(执行对象的toString()方法)
示例程序
//自定义类型转换器????
public class StringToPhoneNumberConverter implements Converter<String, PhoneNumberModel> {
Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");
@Override
public PhoneNumberModel convert(String source) {
// 如果string为空
if (!StringUtils.hasLength(source)) {
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));
}
}
}
修改spring配置文件:
<!-- 注解 HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<!-- 注册ConfigurableWebBindingInitializer -->
<property name="webBindingInitializer" ref="webBindingInitializer" />
</bean>
<!-- 注册ConversionService和自定义类型转换器 -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<bean
class="cn.yue.mvc.anno.controller.support.converter.StringToPhoneNumberConverter" />
</list>
</property>
</bean>
<!-- 使用ConfigurableWebBindingInitializer注册conversionService -->
<bean id="webBindingInitializer"
class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="conversionService" ref="conversionService" />
</bean>
----------------------数据格式化
格式化转换器????
1 printer: 格式化显示接口
public interface Printer<T> {
String print(T object, Locale locale);
}
2 parser: 解析接口
public interface Parser<T> {
T parse(String text, Locale locale) throws ParseException;
}
3 Formatter: 格式化spi接口
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
4 annotationFormatterFactory:注册驱动的字段格式化工厂
//可以识别的注解类型
public interface AnnotationFormatterFactory<A extends Annotation> {
//可以被 A 注解类型注解的字段类型集合
Set<Class<?>> getFieldTypes();
//根据 A 注解类型和 fieldType 类型获取 Printer
Printer<?> getPrinter(A annotation, Class<?> fieldType);
//根据 A 注解类型和 ieldType 类型获取 Parser
Parser<?> getParser(A annotation, Class<?> fieldType);
格式化转换器的注册和使用
FormatterRegistry:注册格式化转换器
FormattingConversionService:运行时类型转换和格式化服务接口
spring内建格式化转换器
类名 说明
DateFormatter java.util.Date<---->String 实现日期的格式化/解析
NumberFormatter java.lang.Number<---->String 实现通用样式的格式化/解析
CurrencyFormatter java.lang.BigDecimal<---->String 实现货币样式的格式化/解析
PercentFormatter java.lang.Number<---->String 实现百分数样式的格式化/解析
NumberFormatAnnotationFormatterFactory @NumberFormat 注解类型的数字字段类型<---->String
①通过@NumberFormat 指定格式化/解析格式
②可以格式化/解析的数字类型:Short、Integer、Long、Float、Double、BigDecimal、BigInteger
JodaDateTimeFormatAnnotationFormatterFactory @DateTimeFormat 注解类型的日期字段类型<---->String
①通过@DateTimeFormat 指定格式化/解析格式
②可以格式化/解析的日期类型:
joda 中 的 日 期 类 型 ( org.joda.time 包 中 的 ): LocalDate 、
LocalDateTime、LocalTime、ReadableInstant
java 内置的日期类型:Date、Calendar、Long
classpath 中必须有 Joda-Time 类库,否则无法格式化日期类型
示例程序:
//自定义formatter进行解析和格式化????
public class PhoneNumberFormatter implements Formatter<PhoneNumberModel> {
Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");
@Override
public String print(PhoneNumberModel phoneNumber, Locale locale) {
if (phoneNumber == null) {
return "";
}
return new StringBuilder().append(phoneNumber.getAreaCode()).append("-").append(phoneNumber.getPhoneNumber()).toString();
}
@Override
public PhoneNumberModel parse(String text, Locale locale) throws ParseException {
// 如果 source 为空 返回 null
if (!StringUtils.hasLength(text)) {
return null;
}
Matcher matcher = pattern.matcher(text);
// 如果匹配 进行转换
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]", text));
}
}
}
字段级别的格式化?????
public class FormatterModel {
@NumberFormat(style = Style.NUMBER, pattern = "#,###")
private int totalCount;
@NumberFormat(style = Style.PERCENT)
private double discount;
@NumberFormat(style = Style.CURRENCY)
private double sumMoney;
@DateTimeFormat(iso = ISO.DATE)
private Date registerDate;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date orderDate;
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public double getDiscount() {
return discount;
}
public void setDiscount(double discount) {
this.discount = discount;
}
public double getSumMoney() {
return sumMoney;
}
public void setSumMoney(double sumMoney) {
this.sumMoney = sumMoney;
}
public Date getRegisterDate() {
return registerDate;
}
public void setRegisterDate(Date registerDate) {
this.registerDate = registerDate;
}
public Date getOrderDate() {
return orderDate;
}
public void setOrderDate(Date orderDate) {
this.orderDate = orderDate;
}
}
测试:
public void testFormatterModel() throws SecurityException, NoSuchFieldException {
// 默认自动注册对@NumberFormat和@DateTimeFormat的支持
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
// 准备测试模型对象
FormatterModel model = new FormatterModel();
model.setTotalCount(10000);
model.setDiscount(0.51);
model.setSumMoney(10000.13);
model.setRegisterDate(new Date(2012 - 1900, 4, 1));
model.setOrderDate(new Date(2012 - 1900, 4, 1, 20, 18, 18));
// 获取类型信息
TypeDescriptor descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("totalCount"));
TypeDescriptor stringDescriptor = TypeDescriptor.valueOf(String.class);
Assert.assertEquals("10,000", conversionService.convert(model.getTotalCount(), descriptor, stringDescriptor));
Assert.assertEquals(model.getTotalCount(), conversionService.convert("10,000", stringDescriptor, descriptor));
}
}
自定义注解进行字段级别的解析/格式化????
自定义注解
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
}
实现annotationFormatterFactor注册格式化工厂
@PhoneNumber
private PhoneNumberModel phoneNumber;
测试用例
---------------------------数据验证
控制器
@Controller
public class RegisterSimpleFormController {
private UserModelValidator validator = new UserModelValidator();
/**
* 暴露表单引用对象为模型数据
*
* @return
*/
@ModelAttribute("user")
public UserModel getUser() {
return new UserModel();
}
/**
* 表单展示
*
* @return
*/
@RequestMapping(value = "/annoValidator", method = RequestMethod.GET)
public String showRegisterForm() {
System.out.println("anno showRegisterForm");
return "validate/registerAndValidator";
}
/**
* 表单提交
*
* @param user
* @param errors
* @return
*/
@RequestMapping(value = "/annoValidator", method = RequestMethod.POST)
public String submitForm(@ModelAttribute("user") UserModel user, Errors errors) {
System.out.println("anno submitForm");
// 调用UserModelValidator的validate方法进行验证
validator.validate(user, errors);
// 如果有错误再回到表单展示页面
if (errors.hasErrors()) {
return showRegisterForm();
}
return "redirect:/success";
}
}
修改spring 配置文件
<!-- 基于注解的方式实现数据验证 -->
<bean class="cn.yue.mvc.anno.controller.RegisterSimpleFormController" />
页面视图
/jsp/registerAndValidator.jsp (复制之前所用)
声明式数据验证??????
添加验证框架
参考:http://jinnianshilongnian.iteye.com/blog/1752171