应用程序在执行业务逻辑前,必须通过数据校验保证接收到的输入数据是正确合法的,如代表生日的日期应该是一个过去的时间、工资的数值必须是一个正数等。一般情况下,应用程序的开发是分层的,不同层的代码由不同的开发人员负责。很多时候,同样的数据验证会出现在不同的层中,这样就会导致代码冗余,为了避免这样的情况,最好将验证逻辑和相应的域模型进行绑定,将代码验证的逻辑集中起来管理。
8.1 JSR 303
JSR-303是 Java为 Bean 数据合法性校验所提供的标准框架,它已经包含在Java EE 6.0中。JSR-303通过在 Bean属性上标注类似于@NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。可以通过 http∶/jcp.org/en/jsr/detail?id=303 了解JSR-303的详细内容。JSR-303定义了一套可标注在成员变量、属性方法上的校验注解,说明如表所示:
注解 | 说明 |
@Null | 验证对象是否为null |
@NotNull | 验证对象是否不为null,无法检查长度为0的字符串,用于验证基本数据类型 |
@NotBlank | 检查约束字符串是不是null,被trim的长度是否大于0,值作用于字符串,并且会去除前后空格 |
@AssertTrue | 验证Boolean对象是否为true |
@AssertFalse | 验证Boolean对象是否为false |
@Max(value) | 验证Number和String对象是否小于等于指定的值 |
@Min(value) | 验证Number和String对象是否大于等于指定的值 |
@DecimalMax(value) | 被标注的值必须不大于约束中指定的最大值。这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示,小数存在精度 |
@DecimalMin(value) | 被标注的值必须不小于约束中指定的最小值。这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示,小数存在精度 |
@Digits(integer,fcaction) | 验证字符串是否是符合指定格式的数字,integer指定整数精度,fraction指定小数精度 |
@Size(min,max) | 验证对象(Array、Collection、Map、String)长度是否在给定的范围之内 |
@Past | 验证Date和Calender对象是否在当前时间之前 |
@Future | 验证Date和Calender对象是否在当前时间之后 |
@Pattern | 验证String对象是否符合正则表达式的规则 |
Hibernate Validator是JSR-303的一个参考实现,它除了支持所有标准的校验注解外,还支持如表所示的扩展注解。
@NotBlank | 检查约束字符串是不是Null,被Trim的长度是否大于0。只对字符串,且去掉前后空格 |
@URL | 验证是否是合法的url |
验证是否是合法的邮件地址 | |
@CreditCardNumber | 验证是否是合法的信用卡号码 |
@Length(min,max) | 验证字符串的长度必须在指定的范围内 |
@NotEmpty | 检查元素是否为NULL或者EMPTY。用于Array、Collection、Map、String |
@Range(min,max,message) | 验证属性值必须在合适的范围内 |
JSR-303 的核心接口是
javax.validation.Validator,该接口根据目标对象类中所标注的校验注解进行数据校验,并得到校验结果。
8.2 SpringMVC数据验证
8.2.1 使用注解
下面将以用户登录为例进行讲解,首先添加如下依赖:
org.hibernate.validator
hibernate-validator
6.2.0.Final
接下来新建VO类,使用注解
@Data
public class UserVO {
@Max(value = 12,message = "用户名不能超过12位")
@Min(value = 6,message = "用户名不能少于6位")
private String username;
@Pattern(regexp = "\\w{6,12}")
private String password;
}
在上例的VO中,使用了@Max,@Min注解用于验证用户名长度必须在6-12位之间。验证密码时则使用了@Pattern注解,该注解可以以正则表达式来验证一个字符串是否符合标准,上例中的正则表达式表明字符串必须由6-12的数字、字母、下划线组成。
新建页面,用于填写用户名和密码:
接下来,新建Controller进行测试。
package cn.bytecollege.controller;
import cn.bytecollege.vo.UserVO;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.w3c.dom.stylesheets.LinkStyle;
import javax.validation.Valid;
import java.util.List;
@Controller
public class LoginController {
@PostMapping("/login")
public String login(@Valid UserVO userVO, BindingResult result, Model model){
if(result.hasErrors()){
if(result.hasFieldErrors("username")){
List list = result.getFieldErrors("username");
String msg = "";
for (FieldError e:list) {
msg+=e.getDefaultMessage();
}
model.addAttribute("usernameMessage",msg);
}
if(result.hasFieldErrors("password")){
FieldError error = result.getFieldError("password");
model.addAttribute("passwordMessage",error.getDefaultMessage());
}
}
return "login.jsp";
}
}
部署该应用,访问登录页面,在不填写任何内容的情况下提交表单,运行结果如下图:
在上例中可以看出在Controller的方法参数User上使用了@Valid注解,这个注解用于表明对该参数进行验证。而BindingResult对象则是用于获取验证的信息,SpringMVC会将校验结果保存在该对象。该对象主要有如下几个方法:
- FieldError getFieldError(String field)∶根据属性名获取对应的校验错误。
- List
getFieldErrors()∶获取所有的属性校验错误。 - Object getFieldValue(String field)∶获取属性值。
- int getErrorCount()∶获取错误数量。
8.3 拦截器
当收到请求时,DispatcherServlet 将请求交给处理器映射(HandlerMapping),让它找出对应该请求的 HandlerExecutionChain 对象。在讲解 HandlerMapping 之前,有必要认识一下这个 HandlerExecutionChain 对象。HandlerExecutionChain 顾名思义是一个执行链,它包含一个处理该请求的处理器(Handler),同时包括若干个对该请求实施拦截的拦截器(HandlerInterceptor)。当HandlerMapping 返回 HandlerExecutionChain 后,DispatcherServlet 将请求交给定义在HandlerExecutionChain 中的拦截器和处理器一并处理。HandlerExecutionChain 是负责处理请求并返回 ModelAndView 的处理执行链,其结构如图所示。请求在被 Handler 执行的前后,链中装配的 HandlerInterceptor 会实施拦截操作。
8.3.1拦截器方法
拦截器到底做了什么事情?我们通过考查拦截器的几个接口方法进行了解。
- boolean preHandle(HttpServletRequest request, HttpServletResponse response, Objecthandler)∶在请求到达 Handler 之前,先执行这个前置处理方法。当该方法返回false 时,请求直接返回,不会传递到链中的下一个拦截器,更不会传递到处理器链末端的 Handler中。只有返回 true 时,请求才向链中的下一个处理节点传递。
- void postHandle(HttpServletRequest request, HttpServletResponse response, Objecthandler,ModelAndView modelAndView)∶在请求被HandlerAdapter执行后,执行这个后置处理方法。
- void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler,Exception ex)∶在响应已经被渲染后,执行该方法。位于处理器链末端的是一个 Handler,DispatcherServlet 通过 HandlerAdapter 适配器对 Handler 进行封装,并按统一的适配器接口对 Handler 处理方法进行调用。
8.3.2 拦截器使用
下面的示例将演示拦截器的使用:
首先,定义拦截器:
package cn.bytecollege.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
/**
* preHandle方法是进行处理器拦截用的,该方法将在Controller方法
* 被调用前被调用,该方法的返回值为true时拦截器才会继续往下执行,
* 该方法的返回值为false时,整个请求就结束了。
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("执行了preHandle方法");
return true;
}
/**
* 该方法将在Controller方法调用后执行,方法可以对ModelAndView进行操作,
* 该方法也只能在当前Interceptor的preHandle方法的返回值为true时才会执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("执行了postHandle方法");
}
/**
* 该方法将在整个请求完成之后执行,作用是清理资源,
* 该方法也只能在当前 Interceptor的preHandle方法的返回值为true时才会执行。
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("执行了afterCompletion方法");
}
}
定义完拦截器后,需要在配置文件中进行如下配置,才能使拦截器生效:
运行结果如下图:
本文暂时没有评论,来添加一个吧(●'◡'●)