目录
前言
当涉及到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 |
4.@Validated与@Valid区别
@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>
测试
登录
退出