springMVC学习笔记七(基于注解方式的控制器的数据验证,类型转换和格式化)

===================基于注解方式的控制器的数据验证,类型转换和格式化=========




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





原文地址:https://www.cnblogs.com/retacn-yue/p/6194262.html