您的位置:首页 > 编程语言 > Java开发

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

2014-08-06 14:04 211 查看
===================基于注解方式的控制器的数据验证,类型转换和格式化=========

-----------------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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐