一、数据校验
项目中涉及到数据校验,如果只做前端校验是不安全的,我们可以绕过前端校验,重要数据一定要加上后端校验;
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 来读取国际化信息。