1. 数据校验、数据格式化
参考博客 http://www.importnew.com/19477.html
1.1 数据校验
使用 spring 数据校验,先要导入校验器的 jar:
<!--数据校验-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.2.Final</version>
</dependency>
此处使用的 hibernate 校验器
JSR 规范:
在实体类的属性上添加注解,可以完成数据校验:
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
------------------------------
Hibernate Validator 附加的注解
@NotBlank(message =) 验证字符串非 null,且长度必须大于 0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
如 Employee 实体类中:
public class Employee { {
private Integer id;
@NotEmpty( message = " " 用户名不能为空" ")
@Size( min = 3 3, max = 6 6, message = " " 姓名长度应在 {min}- - {max}")
private String name;
@Min( value = 2700, message = " " 工资不能少于 {value}")
@Max( value = 10000, message = " " 工资不能超过 {value}")
private Float salary;
在 springMVC-servlet.xml 中配置校验器:
<!-- 配置校验器 -->
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!-- 校验器,使用 hibernate 校验器 -->
<property name="providerClass"
value="org.hibernate.validator.HibernateValidator"/>
</bean>
1.1.1 简单的数据校验
新建 ValidateController
@Controller
public class ValidateController { {
@RequestMapping( "/val1.html")
public M M odelAndView validate1( @Validated Employee emp, BindingResult result) { {
if ( result. hasErrors()) { { // 如果验证错误
FieldError nameError = result. getFieldError( "name");
FieldError salaryError = result. getFieldError( "salary");
ModelAndView view = new ModelAndView();
view.setViewName( "/validate.jsp"); // 如果 有错就 返回原页面
if ( nameError != null) { {
view.addObject( "nameError", nameError.getDefaultMessage());
} }
if ( salaryError != null) { {
view.addObject( "salaryError", salaryError.getDefaultMessage());
} }
return view;
} }
// 验证成功去首页
return new ModelAndView( "/index.jsp");
} }
} }
@Validated 修饰的参数会被按照规则校验。BindingResult 会存放校验信息。在需要校验的 pojo 前边添加@Validated,在需要校验的 pojo 后边添加 BindingResultbindingResult 接收校验出错信息注意:@Validated 和 BindingResult bindingResult 是配对出现,并且形参顺序是固定的(一前一后)
页面 validate.jsp
<form action="/val1.html" method="post">
name:<input type="text" name="name"/>${nameError}<br/>
salary:<input type="text" name="salary"/>${salaryError}<br/>
<input type="submit" value="提交"/>
</form>
运行结果:
提交后:
1.1.2 使用@ModelAttribute 和<form:>
spring 有自定义的表单标签,<form:>冒号后面的是生成 html 的标签名,如<form:input>就会生成一个<input>:
<form:form modelAttribute="empModel" method="post" action="/val2.html">
name:<form:input path="name" /><br/>
<!--输出 name 的校验信息-->
<form:errors path="name"></form:errors><br/>
salary:<form:input path="salary" /><br/>
<form:errors path="salary"></form:errors><br/>
<input type="submit" value="Submit" /><br/>
<!--输出所有错误信息-->
<form:errors path="*"></form:errors>
</form:form>
使用这个标签需要在 jsp 页面中引入头文件:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<form:>标签将的 modelAttribute 与 action 对应的方法上@ModelAttribute 修饰的对象对应。要想进入这个页面,首先要经过一个 Controller 的方法,在 Model 中添加对应的属性:
@RequestMapping( "/goVal2.html")
public String goVal2(Model model) { {
if (! ! model. containsAttribute( "empModel")) { {
l //empModel 与页面中的 <form:form modelAttribute="empModel"> 对应
model. addAttribute( "empModel", new Employee());
} }
return "/validate.jsp";
} }
如果不经过 Controller 或者 Controller 中没有放 empModel 这个属性,那么页面就会报错:
提交表单验证的方法:
@RequestMapping( "/val2.html")
public String test( @Validated @ModelAttribute( "empModel") Employee emp,
BindingResult result, Model model) { {
// 如果有验证错误 返回到 m form 页面
if ( result. hasErrors()) { {
// 经过 2 goVal2 方法的目的是为了设置 l empModel 属性
// 如果 l empModel 属性没有设置,页面就报错了
return goVal2( model);
} }
return "/index.jsp";
} }
1.1.3 数据校验信息国际化
国际化就是根据浏览器默认语言的不同,显示不同的提示信息:
在 Employee 中,给 id 字段添加验证信息:
public class Employee { {
@NotNull( message= "{NotNull.emp.id}")
private Integer
{NotNull.emp.Id}是从 properties 文件中读取属性值
在 resources 目录下创建两个文件,一个用来存放中文提示信息,一个存放英文提示信息:
注意文件的命名,xx.properties 对应的英文配置文件是 xx_en_US.properties
i18n.properties
NotNull.emp.id=id 不能为空
i18n_en_US.properties
NotNull.emp.id=userId can not be null
两个文件的 key 是对应的,值是不同的语言
springMVC-servlet.xml 中配置,其中 id=validator 的 bean 前面已经配置过了,这里再添加一个属性即可
<!-- 配置校验器 -->
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!-- 校验器,使用 hibernate 校验器 -->
<property name="providerClass"
value="org.hibernate.validator.HibernateValidator"/>
<!--这里添加一个校验信息的数据源-->
<property name="validationMessageSource" ref="messageSource" />
</bean>
<!--自动装配校验器-->
<mvc:annotation-driven validator="validator"/>
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<!-- 在 web 环境中一定要定位到 classpath 否则默认到当前 web 应用下找 -->
<value>classpath:i18n</value>
<value>classpath:org/hibernate/validator/ValidationMessages</value>
</list>
</property>
</bean>
使用 1.1.2 节中的测试代码,运行结果:
将浏览器语言切换成英文,刷新页面:
1.2 数据格式化
1.2.1使用注解格式化
springMVC 在映射 Date 类型的属性时会报错:
如果属性是封装在实体类中的,可以使用@DateTimeFormat 注解,如 Employee 中的 hireDate属性。
@DateTimeFormat( pattern = "yyyy- - MM- - dd")
private Date hireDate;
12.2.2 initBinder 实现格式化
@DateTimeFormat 是在实体类中格式化日期类型的属性,所有的 Controller 中用到该实
体类都会自动使用注解定义的格式是格式化数据。除此之外,还可以使用@InitBinder 在
Controller 中自定义格式化:
@Controller
public class DateFormatController { {
// 自定义格式化
@InitBinder
public void initBinder( ServletRequestDataBinder binder) { {
SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy- - MM- - dd");
binder.registerCustomEditor( Date. class,
new CustomDateEditor( dateFormat, true));
} }
@RequestMapping( "/date.html")
public String date( Date date) { {
System. out .println( date);
return "/format.jsp";
} }
} }
@InitBinder 定义的格式化规则对当前 Controller 有效。
1.2.3 自定义格式化
springMVC 有多种方式实现自定义数据格式化,假设现在输入一个电话号码,格式是010-12345678,我们有一个实体类,把区号和电话号码分开:
public class PhoneNumModel { {
private String areaCode; // 区号
private String phoneNumber; // 电话号码
//getter/setter 方法略
} }
当请求中传入一个 String 类型的参数“010-12345678”,通过 springMVC 的数据格式化,可以把 String 转换成实体类 PhoneNumModel
第一种方式:定义一个转换工具类,继承 PropertyEditorSupport
public class PhoneNumConverter implements Converter< < String, PhoneNumModel > {
// 正则表达式,定义数据规则
Pattern pattern = = Pattern.compile( "^(\\ d{3,4})- -( (\\ d{7,8})$");
@Override
public PhoneNumModel convert( String s s) { {
if ( s == null || !StringUtils.hasLength(s s)) { {
return null; // 如果没值,设值为 null
} }
Matcher matcher = pattern.matcher(s s);
if ( matcher.matches()) { {
PhoneNumModel phoneNumber = new PhoneNumModel();
phoneNumber.setAreaCode( matcher.group(1 1));
phoneNumber.setPhoneNumber( matcher.group(2 2));
return phoneNumber;
} else { {
throw new IllegalArgumentException( String.format(" " 类型转换失败,需要格
式 [010- - 12345678] ,但格式是 [%s]", s s));
} }
} }
} }
Controller 中使用@InitBinder 注册自定义转换器:
@Controller
public class MyBinderController { {
// 自定义格式化
@InitBinder
public void initBinder( WebDataBinder binder) { {
binder.registerCustomEditor( PhoneNumModel. class, new PhoneNumEditor());
} }
@RequestMapping( "/phone.html") // 注意一定要写 @RequestParam
public ModelAndView phone( @RequestParam( "phone") PhoneNumModel phone) { {
// 绑定成功时可以看到输出
System. out .println( phone.getAreaCode());
System. out .println( phone.getPhoneNumber());
return new ModelAndView( "/format.jsp", "phone", phone);
} }
} }
测试分 url:
http://localhost:8080/phone.html?phone=010-1234567
通过@InitBinder 的方式,数据绑定规则只对当前 Controller 有效
在 Spring4.2 之后提出了一种新的转换方式,工具类实现 Converter 接口:
public class PhoneNumConverter implements Converter< < String, PhoneNumModel > {
// 正则表达式,定义数据规则
Pattern pattern = Pattern.compile( "^(\\ d{3,4})- -( (\\ d{7,8})$");
@Override
public PhoneNumModel convert( String s s) { {
if ( s == null || !StringUtils.hasLength(s s)) { {
return null; // 如果没值,设值为 null
} }
Matcher matcher = pattern.matcher(s s);
if ( matcher.matches()) { {
PhoneNumModel phoneNumber = new PhoneNumModel();
phoneNumber.setAreaCode( matcher.group(1 1));
phoneNumber.setPhoneNumber( matcher.group(2 2));
return phoneNumber;
} else { {
throw new IllegalArgumentException( String.format(" " 类型转换失败,需要格
式 [010- - 12345678] ,但格式是 [%s]", s s));
} }
} }
} }
在 springMVC 配置文件中注册自定义转换器:
<!-- 需要将转换器设置给注解驱动 -->
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean"/>
<bean id="conversionServiceFactoryBean"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="util.PhoneNumConverter"></bean>
</set>
</property>
</bean>
Controller 中不需要写@InitBinder 了。这个配置对所有 Controller 都生效。
2. 其它注解
2.1 @CookieValue
@RequestMapping( "cookie.html")
public String cookie( @CookieValue( value = "JSESSIONID", defaultValue = "mysession")
String jsessionId) { {
System. out .println( jsessionId);
return "/index.jsp";
} }
@CookieValue 用于获取 cookie 信息。value 用于指定 cookie 的名字,defaultValue 是当对应的 cookie 为空时系统设置的默认值。required 设置为 true 表示必须。
上面的代码如果浏览器中没有 cookie,会输出 mysession。再次刷新页面,就会打印出当前的 jsesessionid,如:
2.2 @Value
@Value 可以实现从配置文件中读取数据并注册给 Controller 在 springMVC 配置文件种加载 properties 文件:
<context:property-placeholder location="classpath:*.properties"/>
测试代码,这里读取的是 12.1.3 中配置文件中的 key
// 从配置文件中读取属性
@Value( "${NotNull.emp.id}")
private String NOT_NULL_ID;
@RequestMapping( "value.html")
public String value() { {
System. out .println( NOT_NULL_ID);
return "/index.jsp";
} }
3. 数据绑定流程
- ApplicationContext 初始化时建立所有 url 和 controller 类的对应关系(用 Map 保存);
- 根据请求url找到对应的controller,并从controller中找到处理请求的方法
- Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
- DataBinder 是数据绑定的核心部件,调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet中的请求信息填充到入参对象中
- 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData 对象
- Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参。