前言
当涉及到Web应用程序的开发时,数据校验和拦截器是非常重要的功能。在SpringMVC框架中,我们可以使用JSR303数据校验和拦截器来实现这些功能。本文将详细介绍JSR303数据校验和拦截器的使用方法和原理。
一、JSR303数据校验
1.JSR303是什么
JSR303是Java规范中定义的一套用于数据校验的标准,它提供了一种简单而强大的方式来验证数据的合法性。在Java Web开发中,我们经常需要对用户提交的数据进行校验,以确保数据的有效性和安全性。JSR303提供了一套注解,我们可以通过在实体类的字段上添加这些注解来实现数据校验。
2.为什么要使用JSR303
1. 代码简洁:使用JSR 303可以将数据校验的逻辑从业务代码中分离出来,使代码更加简洁和易于维护。我们只需要在实体类的字段上添加相应的注解,就可以定义校验规则,而不需要编写大量的校验代码。
2. 统一校验规则:通过使用JSR 303,我们可以定义一套统一的校验规则,这样可以确保所有的数据校验都遵循相同的规范。这对于多个模块或团队共同开发的项目尤为重要,可以避免各自实现不一致的校验逻辑。
3. 前后端校验一致:JSR 303支持在前端和后端都进行数据校验。前端可以使用JavaScript框架如jQuery Validation等进行校验,后端可以使用Java框架如SpringMVC等进行校验。这样可以确保前后端的校验规则一致,提高系统的安全性和用户体验。
4. 可扩展性:JSR 303提供了一套标准的校验注解,但也支持自定义注解和校验器。我们可以根据项目的需求,自定义一些特定的校验规则,以满足业务的特殊需求。
3.常用注解
注解 | 说明 |
@Null | 用于验证对象为null |
@NotNull | 用于对象不能为null,无法查检长度为0的字符串 |
@NotBlank | 只用于String类型上,不能为null且trim()之后的size>0 |
@NotEmpty | 用于集合类、String类不能为null,且size>0。但是带有空格的字符串校验不出来 |
@Size | 用于对象(Array,Collection,Map,String)长度是否在给定的范围之内 |
@Length | 用于String对象的大小必须在指定的范围内 |
@Pattern | 用于String对象是否符合正则表达式的规则 |
用于String对象是否符合邮箱格式 | |
@Min | 用于Number和String对象是否大等于指定的值 |
@Max | 用于Number和String对象是否小等于指定的值 |
@AssertTrue | 用于Boolean对象是否为true |
@AssertFalse | 用于Boolean对象是否为false |
显示详细信息
@Validated
和@Valid
是Spring框架中用于数据校验的注解,它们的作用是对方法参数或方法返回值进行校验。尽管它们的功能相似,但在使用上有一些区别。
- 适用范围:
@Valid
注解适用于方法参数和方法返回值的校验。@Validated
注解适用于方法参数的校验。
- 校验器的选择:
@Valid
注解使用的是Java Bean Validation(JSR 380)规范定义的校验器,例如Hibernate Validator。@Validated
注解使用的是Spring框架自带的校验器,例如Spring Validator。
- 分组校验:
@Valid
注解支持分组校验,可以根据不同的校验场景选择不同的校验规则。@Validated
注解不支持分组校验,只能使用默认的校验规则。
- 错误处理:
@Valid
注解的校验错误会被封装成MethodArgumentNotValidException
或ConstraintViolationException
异常,并由全局异常处理器进行处理。@Validated
注解的校验错误会被封装成BindException
或ConstraintViolationException
异常,并由全局异常处理器进行处理。
5.入门案例
1.导入依赖
<!-- JSR303 --> <hibernate.validator.version>6.0.7.Final</hibernate.validator.version> <!-- JSR303 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>${hibernate.validator.version}</version> </dependency>
2.配置校验规则
package com.ctb.model; import lombok.ToString; import org.hibernate.validator.constraints.NotBlank; import javax.validation.constraints.NotNull; @ToString public class User { @NotNull(message = "用户编号不能为空") private Integer id; @NotBlank(message = "用户名不能为空") private String uname; private String upic; public User(Integer id, String uname, String upic) { this.id = id; this.uname = uname; this.upic = upic; } public User() { super(); } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } public String getUpic() { return upic; } public void setUpic(String upic) { this.upic = upic; } }
3.编写校验方法
// 给数据添加服务端校验 @RequestMapping("/valiAdd") public String valiAdd(@Validated User user, BindingResult result, HttpServletRequest req){ // 如果服务端验证不通过,有错误 if(result.hasErrors()){ // 服务端验证了实体类的多个属性,多个属性都没有验证通过 List<FieldError> fieldErrors = result.getFieldErrors(); Map<String,Object> map = new HashMap<>(); for (FieldError fieldError : fieldErrors) { // 将多个属性的验证失败信息输送到控制台 System.out.println(fieldError.getField() + ":" + fieldError.getDefaultMessage()); map.put(fieldError.getField(),fieldError.getDefaultMessage()); } req.setAttribute("errorMap",map); }else { this.userBiz.insertSelective(user); return "redirect:list"; } return "user/edit"; }
4.编写前端页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>编辑信息</h1> <form action="${pageContext.request.contextPath }/${empty b ? 'user/valiAdd' : 'user/edit'}" method="post"> 用户编号:<input type="text" name="id" value="${b.id }"> <span style="color: red;">${errorMap.id}</span><br> 用户名称:<input type="text" name="uname" value="${b.uname }"> <span style="color: red;">${errorMap.uname}</span><br> 用户头像:<input type="text" name="upic" value="${b.upic }"><br> <input type="submit"> </form> </body> </html>
5.测试
二、拦截器(interceptor)
1.什么是拦截器
SpringMVC的处理器拦截器,类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。依赖于web框架,在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个 controller生命周期之内可以多次调用。
2.拦截器与过滤器的区别
什么是过滤器(Filter)
依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等
- 拦截器是基于Java的反射机制实现的,而过滤器是基于Servlet规范实现的。
- 拦截器只能拦截SpringMVC的请求,而过滤器可以拦截所有的请求。
- 拦截器可以访问控制器的方法和参数,而过滤器只能访问请求和响应对象。
- 拦截器可以在请求处理前后进行处理,而过滤器只能在请求处理前进行处理。
3.拦截器的应用场景及作用
- 权限验证:拦截器可以检查用户是否具有访问某个资源的权限。
- 日志记录:拦截器可以记录请求的详细信息,例如请求路径、请求参数等。
- 性能监控:拦截器可以统计请求的处理时间,用于性能监控和优化。
- 异常处理:拦截器可以捕获控制器处理过程中的异常,并进行相应的处理。
拦截器的作用是在请求到达控制器之前进行预处理,例如验证用户权限、记录日志等;在控制器处理完请求后进行后处理,例如记录响应日志、处理异常等。
4.快速入门
- 创建拦截器
首先,创建一个实现HandlerInterceptor接口的拦截器类。该接口定义了三个方法,分别是preHandle、postHandle和afterCompletion,用于在请求处理前、请求处理后以及请求完成后进行拦截和处理。
package com.ctb.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class OneInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("【OneInterceptor】:preHandle..."); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("【OneInterceptor】:postHandle..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("【OneInterceptor】:afterCompletion..."); } }
- 配置拦截器
在SpringMVC的配置文件中,配置拦截器的Bean,并指定拦截的路径。
<!-- 配置拦截器--> <mvc:interceptors> <bean class="com.ctb.interceptor.OneInterceptor"></bean> </mvc:interceptors>
- 测试
在运行测试时会发现它们的运行顺序为
preHandle --> postHandle --> afterCompletion
注意:拦截器会根据preHandle()方法返回值进行拦截判断,返回了一个true
值。这个返回值表示该拦截器已经处理了当前的请求,并且可以继续向下传递请求。如果返回false
,则表示该拦截器不处理当前请求,请求将被终止。
5.拦截器工作原理
- preHandle:用于对拦截到的请求进行预处理,方法接收布尔(true,false)类型的返回值,返回true:放行,false:不放行。
执行时机:在处理器方法执行前执行
- 方法参数
参数 | 说明 |
request | 请求对象 |
response | 响应对象 |
handler | 拦截到的方法处理 |
- postHandle:用于对拦截到的请求进行后处理,可以在方法中对模型数据和视图进行修改
执行时机:在处理器的方法执行后,视图渲染之前
- 方法参数
参数 | 说明 |
request | 请求对象 |
response | 响应对象 |
handler | 拦截到的处理器方法 |
ModelAndView | 处理器方法返回的模型和视图对象,可以在方法中修改模型和视图 |
- afterCompletion:用于在整个流程完成之后进行最后的处理,如果请求流程中有异常,可以在方法中获取对象
执行时机:视图渲染完成后(整个流程结束之后)
- 方法参数
参数 | 说明 |
request | 请求参数 |
response | 响应对象 |
handler | 拦截到的处理器方法 |
ex | 异常对象 |
6.拦截器链
如果多个拦截器能够对相同的请求进行拦截,则多个拦截器会形成一个拦截器链,主要理解拦截器链中各个拦截器的执行顺序。拦截器链中多个拦截器的执行顺序,根拦截器的配置顺序有关,先配置的先执行。
- 创建拦截器
package com.ctb.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class TwoInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("【TwoInterceptor】:preHandle..."); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("【TwoInterceptor】:postHandle..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("【TwoInterceptor】:afterCompletion..."); } }
- 配置拦截器
<!-- 配置拦截器--> <mvc:interceptors> <!--2) 多拦截器(拦截器链)--> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.ctb.interceptor.OneInterceptor"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/user/**"/> <bean class="com.ctb.interceptor.TwoInterceptor"/> </mvc:interceptor> </mvc:interceptors>
- 测试
7.拦截器登录权限案例
- 创建拦截器
package com.ctb.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("【implements】:preHandle..."); StringBuffer url = request.getRequestURL(); if (url.indexOf("/login") > 0 || url.indexOf("/logout") > 0){ // 如果是 登录、退出 中的一种 return true; } // 代表不是登录,也不是退出 // 除了登录、退出,其他操作都需要判断是否 session 登录成功过 String uname = (String) request.getSession().getAttribute("uname"); if (uname == null || "".equals(uname)){ response.sendRedirect("/page/login"); return false; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
- 配置拦截器
<!-- 配置拦截器--> <mvc:interceptors> <bean class="com.ctb.interceptor.LoginInterceptor"></bean> </mvc:interceptors>
- 编写controller层处理登录的方法
package com.ctb.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; /** * @author 彪 * @remark * @create 2023 */ @Controller public class LoginController { @RequestMapping("/login") public String login(HttpServletRequest req){ String uname = req.getParameter("uname"); HttpSession session = req.getSession(); if ("ctb".equals(uname)){ session.setAttribute("uname",uname); } return "redirect:/user/list"; } @RequestMapping("/logout") public String logout(HttpServletRequest req){ req.getSession().invalidate(); return "redirect:/user/list"; } }
- 编写前端页面
<%-- Created by IntelliJ IDEA. User: 86155 Date: 2023/9/13 Time: 6:05 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>用户登录</h1> <form action="/login" method="post"> 用户:<input name="uname"> <input type="submit"> </form> </body> </html>
- 测试
登录
退出