一、数据校验
项目中涉及到数据校验,如果只做前端校验是不安全的,我们可以绕过前端校验,重要数据一定要加上后端校验;
1、通过程序,每个数据取出,进行校验,如果失败直接来到添加页面,提示其重新填写;(不推荐)
2、SpringMVC:可以利用 JSR303 做数据校验;
二、如何校验
1、JSR 303
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中 。
JSR 303 (Java Specification Requests意思是Java 规范提案)通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证:
2、Hibernate Validator 扩展注解
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
3、Spring MVC 数据校验
Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架;
Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在 Spring MVC 中,可直接通过注解驱动的方式进行数据校验;
Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在 Spring 容器中定义了一个 LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean 中;
Spring 本身并没有提供 JSR303 的实现,所以必须将 JSR303 的实现者的 jar 包放到类路径下;
<mvc:annotation-driven /> 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Valid 注解即可让 Spring MVC 在完成数据绑定后执行数据校验的工作;
在已经标注了 JSR303 注解的表单/命令对象前标注一个 @Valid,Spring MVC 框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验;
三、实现数据校验
1、使用JSR 303验证标准
2、加入hibernate validator验证框架
3、在SpringMVC配置文件中增加< mvc:annotation-driven />
4、需要在bean的属性上增加对应验证的注解
5、在目标方法bean类型的前面增加@Valid注解
四、实验代码
1、添加依赖
Maven 方式:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
jar 包方式:
hibernate-validator-5.0.0.CR2.jar
hibernate-validator-annotation-processor-5.0.0.CR2.jar
classmate-0.8.0.jar
jboss-logging-3.1.1.GA.jar
validation-api-1.1.0.CR1.jar
注意:推荐使用 Tomcat7.0,Tomcat7.0以上的 el 表达式比较强大;
2、给验证属性上增加验证注解
public class Employee {
private Integer id;
@NotBlank
@NotEmpty
@Length(min = 6, max = 10, message = "姓名超出长度限制")
private String lastName;
@NotBlank
@Email
@Length(max = 15, message = "电子邮件超出长度限制")
private String email;
private Integer gender;
//可以规定页面提交的日期格式
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past //必须是一个过去的时间
//@Future //必须是一个未来的时间
private Date birth;
//假设页面,为了显示方便提交的工资 10000.98,---》 ¥10,000.98
@NumberFormat(pattern="#,###,###.##")
private Double salary;
private Department department;
}
3、在SpringMVC封装对象的时候,告诉SpringMVC这个 JavaBean 需要校验
/**
* 保存员工
* 增加@Valid注解,验证失败会报错。
* @param employee
* @return
*/
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String addEmp(@Valid Employee employee) {
System.out.println("employee = " + employee);
return "success";
}
4、如何知道校验结果?
给需要校验的 JavaBean 后面紧跟一个 BindingResult,这个BindingResult 就是封装了前一个Bean的校验结果而且不能间隔任何参数。
/**
* 保存员工
* 增加@Valid注解,验证失败会报错。
* @param employee
* @return
*/
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String addEmp(@Valid Employee employee, BindingResult result) {
System.out.println("employee = " + employee);
//是否有校验错误
if (result.hasErrors()) {
System.out.println("有校验错误!!!");
return "add";
} else {
//employeeDao.save(employee);
System.out.println("保存成功!");
return "success";
}
}
根据不同的校验结果然后决定如何操作
五、错误信息的显示
1、关于错误信息
public interface BindingResult extends Errors
Spring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或 Errors 类型,这两个类都位于 org.springframework.validation 包中;
需校验的 Bean 对象和其绑定结果对象或错误对象是成对出现的,它们之间不允许声明其他的入参;
Errors 接口提供了获取错误信息的方法,如 getErrorCount() 或 getFieldErrors(String field)
BindingResult 扩展了 Errors 接口
在目标方法中获取校验结果:
在表单/命令对象类的属性中标注校验注解,在处理方法对应的入参前添加 @Valid,Spring MVC 就会实施校验并将校验结果保存在被校验入参对象之后的 BindingResult 或 Errors 入参中。
常用方法:
FieldError getFieldError(String field)
List<FieldError> getFieldErrors()
Object getFieldValue(String field)
Int getErrorCount()
2、使用表单标签回显错误结果
Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”;
即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在 “隐含对象” 中;
隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息
在 JSP 页面上可通过 <form:errors path=“userName”> 显示错误消息
使用 <form:errors> 标签可以显示该属性的错误提示:
<form:errors path="属性"/>
示例:
在表单上页面上显示所有的错误消息:
<!-- 显示所有的错误消息 -->
<form:errors path="*"/>
显示某一个表单域的错误消息:
<form:errors path="lastName"/>
显示校验的提示信息:
<form:form action="${ctx}/emp" modelAttribute="employee" method="POST">
lastName:<form:input path="lastName" /> <form:errors path="lastName"/> <br/>
email:<form:input path="email" /> <form:errors path="email"/><br/>
gender:<br>
男 <form:radiobutton path="gender" value="1"/><br/>
女 <form:radiobutton path="gender" value="0" /><br/>
birth:<form:input path="birth" /> <form:errors path="birth"/><br/>
salary:<form:input path="salary" /><br/>
dept:
<form:select path="department.id" items="${depts}" itemLabel="departmentName" itemValue="id"></form:select>
<br>
<input type="submit" value="保存">
</form:form>
3、将校验信息回写
BindingResult 中封装了类型转换与格式化的错误信息,可以把错误信息放在请求域中,带回给页面。
示例:
/**
* 保存员工
* 增加@Valid注解,验证失败会报错。
* @param employee
* @return
*/
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String addEmp(@Valid Employee employee, BindingResult result, Model model) {
System.out.println("employee = " + employee);
Map<String, Object> errorsMap = new HashMap<>();
//是否有校验错误
if (result.hasErrors()) {
System.out.println("有校验错误!!!");
List<FieldError> errors = result.getFieldErrors();
for (FieldError fieldError : errors) {
System.out.println("错误消息提示:" + fieldError.getDefaultMessage()); //错误信息
System.out.println("错误的字段是:" + fieldError.getField()); //错误字段
System.out.println(fieldError);
errorsMap.put(fieldError.getField(), fieldError.getDefaultMessage());
}
model.addAttribute("errorInfo", errorsMap);
return "add";
} else {
//employeeDao.save(employee);
System.out.println("保存成功!");
return "success";
}
}
五、提示消息的国际化
每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的 FieldError 对象。
当一个属性校验失败后,校验框架会为该属性生成 4 个消息代码,这些代码以校验注解类名为前缀,结合 modleAttribute、属性名及属性类型名生成多个对应的消息代码:
例如 User 类中的 password 属性标注了一个 @Pattern 注解,当该属性值不满足 @Pattern 所定义的规则时, 就会产生以下 4 个错误代码:
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看 WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:
required:必要的参数不存在。如 @RequiredParam(“param1”) 标注了一个入参,但是该参数不存在
typeMismatch:在数据绑定时,发生数据类型不匹配的问题
methodInvocation:Spring MVC 在调用处理方法时发生了错误
注册国际化资源文件
国际化资源文件怎么写呢?对应的 key 是什么?
每一个字段发生错误以后,都会有自己的错误代码;国际化文件中错误消息的 key 必须对应一个错误代码;
错误信息:
Field error in object 'employee' on field 'email': rejected value [aa];
codes [Email.employee.email,Email.email,Email.java.lang.String,Email];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable:
codes [employee.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@4e69aec9,.*];
default message [不是一个合法的电子邮件地址]
codes:
Email.employee.email 校验规则(@Email).隐含模型中这个对象的key.对象的属性
Email.email 校验规则.属性名
Email.java.lang.String 校验规则.属性类型
Email 校验规则
说明:
1、如果隐含模型中 employee 对象的 email 字段发生了 @Email 校验错误,就会生成 Email.employee.email;
2、Email.email:所有的 email 属性只要发生了 @Email 校验错误;
3、Email.java.lang.String:只要是 String 类型发生了 @Email 校验错误;
4、Email:只要发生了 @Email 校验错误;
注意:如果根据 codes 配置了多个值,精确匹配的 codes 优先生效;
六、提示消息的国际化实验
1、定义国际化资源文件
中文:errors_zh_CN.properties
Email.email=邮箱错误!!
Email=邮箱不正确!!
NotEmpty=不允许为空!!
Length.java.lang.String=长度错误!!
Past=必须是一个过去时间!!
typeMismatch.employee.birth=生日格式不正确!!
typeMismatch=格式不正确!!
Length=长度错误!!
英文:errors_en_US.properties
Email.email=email incorrect~~
Email=email error~~
NotEmpty=must not empty~~
Length.java.lang.String=length incorrect {0},must between {2} and {1}~~
Past=must a past time~~
typeMismatch.employee.birth=birthday incorrect~~
typeMismatch=formatting incorrect~~
Length=length incorrect~~
2、声明国际化资源配置(让SpringMVC来管理国际化资源文件)
<!--管理国际化资源文件-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="errors"/>
</bean>
3、错误信息国际化回显
<form:form action="${ctx}/emp" modelAttribute="employee" method="POST">
lastName:<form:input path="lastName" />
<form:errors path="lastName"/> ---> ${errorInfo.lastName}<br/>
email:<form:input path="email" />
<form:errors path="email"/> ---> ${errorInfo.email}<br/>
gender:<br>
男 <form:radiobutton path="gender" value="1"/><br/>
女 <form:radiobutton path="gender" value="0" /><br/>
birth:<form:input path="birth" />
<form:errors path="birth"/> ---> ${errorInfo.birth}<br/>
salary:<form:input path="salary" /><br/>
dept:
<form:select path="department.id" items="${depts}" itemLabel="departmentName" itemValue="id"></form:select>
<br>
<input type="submit" value="保存">
</form:form>
格式或者校验错误后,<form:erros path=""> 就会显示定制的国际化信息。
4、高级国际化
如何在国际化文件中动态的传入参数信息?
示例:
Length.java.lang.String=length incorrect {0},must between {2} and {1}~~
说明:
{0} 永远都是当前属性名
{1} {2} 才是参数信息,并且是按照属性大小写排序的顺序
5、message 指定错误消息
注释上有个属性是message,直接写上错误信息即可,缺点不能国际化
例如:
@NotBlank
@NotEmpty
@Length(min = 6, max = 10, message = "姓名超出长度限制")
private String lastName;
@NotBlank
@Email
@Length(max = 15, message = "电子邮件超出长度限制")
private String email;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past //必须是一个过去的时间
//@Future //必须是一个未来的时间
private Date birth;
6、国际化信息的回显
根据错误信息,然后通过国际化文件,把信息回显到页面
/**
* 保存员工
* 增加@Valid注解,验证失败会报错。
* @param employee
* @return
*/
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String addEmp(@Valid Employee employee, BindingResult result, Model model,
@RequestHeader("Accept-Language") String language) {
Locale locale = getLocale(language);
System.out.println("locale = " + locale);
System.out.println("employee = " + employee);
Map<String, Object> errorsMap = new HashMap<>();
//是否有校验错误
if (result.hasErrors()) {
System.out.println("有校验错误!!!");
List<FieldError> errors = result.getFieldErrors();
for (FieldError fieldError : errors) {
System.out.println("错误消息提示:" + fieldError.getDefaultMessage()); //错误信息
System.out.println("错误的字段是:" + fieldError.getField()); //错误字段
System.out.println(fieldError);
System.out.println(fieldError.getCode()); //Length
System.out.println(Arrays.toString(fieldError.getCodes())); //[Length.employee.lastName, Length.lastName, Length.java.lang.String, Length]
String message = messageSource.getMessage(fieldError.getCode(), new Object[0], locale);
System.out.println("message = " + message);
errorsMap.put(fieldError.getField(), fieldError.getDefaultMessage());
}
model.addAttribute("errorInfo", errorsMap);
return "add";
} else {
//employeeDao.save(employee);
System.out.println("保存成功!");
return "success";
}
}
private Locale getLocale(String language) {
System.out.println("language = " + language);
Locale locale = null;
if (!StringUtils.isEmpty(language)) {
//locale = en_US,EN;Q=0.9,ZH
String[] split = language.split("-");
locale = new Locale(split[0], split[1]);
}
System.out.println("locale = " + locale);
return locale;
}
错误信息都封装在 FieldError 对象里,其中有 getCodes() 和 getCode() 方法,分别用于获取所有的 codes 值和最后一个 code 值:
同时把国际化文件 MessageSource 注入进来,然后根据请求头的语言信息和 code 来读取国际化信息。