目录
一、JSR303
1、什么是JSR 303?
2、为什么要使用JSR 303?
3、JSR 303常用注解
3.1、常用的JSR 303注解
3.2、@Validated与@Valid区别
3.2.1、@Validated
3.2.2、@Valid
3.2.3、区别
4、使用案例
4.1、导入依赖
4.2、配置校验规则
4.3、编写校验方法
4.4、前端代码
4.5、测试
二、interceptor拦截器
1、什么是拦截器?
2、为什么要使用拦截器?
3、拦截器与过滤器
3.1、什么是过滤器(Filter)
3.2、拦截器与过滤器的区别
3.2.1、 过滤器(filter)
3.2.2、 拦截器(interceptor)
3.2.3、汇总
4、拦截器应用场景
5、使用案例
5.1、创建拦截器
5.2、配置拦截器
5.3、运行测试
5.4、拦截器工作原理
5.5、拦截器链
5.6、登录拦截实例
5.6.1、创建拦截器
5.6.2、配置拦截器
5.6.3、编写控制层
5.6.4、前端页面
5.6.5、测试
登入
登出
一、JSR303
1、什么是JSR 303?
JSR 303是Java规范请求(Java Specification Request)的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。 JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint(约束) 的实现,除此之外还有一些附加的 constraint。
它定义了一套用于Java Bean校验的标准。JSR 303使用注解的方式,在Spring MVC中被广泛应用于进行数据校验和验证。
验证数据是一项常见任务,它发生在从表示层到持久层的所有应用程序层中。通常在每一层都实现相同的验证逻辑,这既耗时又容易出错。为了避免重复这些验证,开发人员经常将验证逻辑直接捆绑到域模型中,将域类与验证代码混在一起,而验证代码实际上是关于类本身的元数据
2、为什么要使用JSR 303?
在前端我们进行了数据校验,可是为什么我们还要做一篇,因为因为一点小失误我们的前端校验没有写好,但是有些人还是会绕过前端发送的请求(通过类似Postman这样的测试工具进行非常数据请求),传递一些错误的参数,这就会让我们的后端代码有大大的危险,所以我们一般都是前端一套校验,后端在一套校验,这样安全性就能够大大得到提升了。
所以我总和了JSR 303以下的好处:
- 提高代码的可维护性:通过在实体类中添加注解,可以清晰地标识出需要进行校验的字段和规则,使代码更易于理解和维护。
- 增强数据的完整性:根据定义的规则,可以自动校验输入数据的合法性,避免错误数据进入系统,保证数据的完整性和准确性。
- 减少重复代码:通过注解,可以在不同场景下重复使用相同的校验规则,减少编写重复代码的工作量。
3、JSR 303常用注解
3.1、常用的JSR 303注解
注解 说明 @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对象是否符合正则表达式的规则 @Email 用于String对象是否符合邮箱格式 @Min 用于Number和String对象是否大等于指定的值 @Max 用于Number和String对象是否小等于指定的值 @AssertTrue 用于Boolean对象是否为true @AssertFalse 用于Boolean对象是否为false
3.2、@Validated与@Valid区别
@Validated 和 @Valid 是用于数据校验的注解,但它们有一些区别和应用场景的差异。
3.2.1、@Validated
- Spring 框架提供的注解
- 支持分组校验
- 可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
- 由于无法加在成员属性(字段)上,所以无法单独完成级联校验,需要配合@Valid
- Spring 提供的校验器,默认使用的是 Hibernate Validator(实现了 JSR-303 规范),但它支持更丰富的校验场景,如分组校验。
3.2.2、@Valid
- JDK提供的 JSR-303(Bean Validation)规范的注解
- 不支持分组校验
- 可以用在方法、构造函数、方法参数和成员属性(字段)上
- 可以加在成员属性(字段)上,能够独自完成级联校验
- 使用 @Valid 注解时,会触发 JSR-303 或者其它支持的校验框架来对被注解的对象进行校验。
3.2.3、区别
- 适用范围:@Valid 可以应用于方法参数、返回值、字段和方法上,而 @Validated 只能应用于类、接口和方法上。
- 校验框架:@Validated 默认使用 Hibernate Validator(实现了 JSR-303)、Spring Validator 或自定义的校验器,而 @Valid 使用的是 JSR-303 或其它支持的校验框架。
- 分组校验:@Validated 支持分组校验,允许在不同的场景下使用不同的校验规则,而 @Valid 不直接支持分组校验。
@Validated 是 Spring 框架提供的扩展注解,并不属于 Java 标准规范。在使用时,可以根据具体的需求选择合适的注解进行数据校验。
4、使用案例
4.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>
4.2、配置校验规则
在实体类里面添加配置校验
package com.tgq.model; import lombok.ToString; import org.hibernate.validator.constraints.NotBlank; import javax.validation.constraints.NotNull; @ToString public class MyStudent { @NotNull(message = "学生编号不能为空") private String sid; @NotBlank(message = "学生名不能为空") private String sname; @NotBlank(message = "学生年龄不能为空") private String sage; @NotBlank(message = "学生性别不能为空") private String ssex; public MyStudent(String sid, String sname, String sage, String ssex) { this.sid = sid; this.sname = sname; this.sage = sage; this.ssex = ssex; } public MyStudent() { super(); } public String getSid() { return sid; } public void setSid(String sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public String getSage() { return sage; } public void setSage(String sage) { this.sage = sage; } public String getSsex() { return ssex; } public void setSsex(String ssex) { this.ssex = ssex; } }
4.3、编写校验方法
使用
@Validated
注解对我们的MyStudent进行服务端校验。
// 给数据添加服务端校验
@RequestMapping("/valiAdd")
public String valiAdd(@Validated MyStudent myStudent, 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.myStudentBiz.insertSelective(myStudent);
return "redirect:stu/list";
}
return "stu/edit";
}
4.4、前端代码
使用form表单进行提交
<%--
Created by IntelliJ IDEA.
User: tgq
Date: 12/9/2023
Time: 下午8:05
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>edit</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/stu/valiAdd" method="post">
用户id:<input type="text" name="sid"><span style="color: red">${errorMap.sid}</span><br>
用户名:<input type="text" name="sname"><span style="color: red">${errorMap.sname}</span><br>
用户年龄:<input type="text" name="sage"><span
style="color: red">${errorMap.sage}</span><br>
用户性别:<input type="text" name="ssex"><span style="color: red">${errorMap.ssex}</span><br>
<input type="submit" value="提交">
</form>
</body>
</html>
4.5、测试
点击提交,如果为空就会提示
二、interceptor拦截器
1、什么是拦截器?
拦截器是在请求进入后端处理程序之前或之后执行特定逻辑的组件。它们能够拦截处理程序执行的流程,允许在请求处理过程中插入额外的功能。
SpringMVC的处理器拦截器,类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。依赖于web框架,在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。
2、为什么要使用拦截器?
- 横向功能扩展:通过拦截器,可以在不修改原有业务逻辑的情况下添加额外的功能,例如日志记录、权限验证、性能统计等。
- 代码复用:多个请求中可能需要相同的处理逻辑,通过拦截器可以将这部分逻辑抽取出来,减少代码的重复编写。
- 解耦合:通过拦截器,可以将关注点分离,在拦截器中处理通用的逻辑,使得业务处理程序更专注于业务本身。
3、拦截器与过滤器
3.1、什么是过滤器(Filter)
依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。
3.2、拦截器与过滤器的区别
3.2.1、 过滤器(filter)
- filter属于Servlet技术,只要是web工程都可以使用
- filter主要由于对所有请求过滤
- filter的执行时机早于Interceptor
3.2.2、 拦截器(interceptor)
- interceptor属于SpringMVC技术,必须要有SpringMVC环境才可以使用
- interceptor通常由于对处理器Controller进行拦截
- interceptor只能拦截dispatcherServlet处理的请求
3.2.3、汇总
- 拦截器是在应用程序处理程序内部执行的,而过滤器则是在应用程序之前或之后执行的。
- 过滤器是基于Servlet规范的,拦截器是基于应用框架的。
- 过滤器可以在请求到达Servlet容器之前对请求进行处理,而拦截器只能在请求到达应用程序之后进行处理。
4、拦截器应用场景
- 权限验证:拦截器可以检查用户是否具有操作的权限,如果没有权限,可以拦截请求并返回相应的错误信息。如果没有直接返回到登录页面。
- 日志记录:拦截器可以记录请求的详细信息,例如请求时间、信息、请求参数等,便于后续的日志分析和故障排查。以便进行信息监控、信息统计、计算PV(Page View)等。
- 性能统计:拦截器可以统计请求的执行时间,便于分析系统性能并进行优化。(如果有反向代理,如apache可以自动记录);
- 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个Controller中的处理方法都需要的,我们就可以使用拦截器实现。
拦截器链是由多个拦截器组成的链式结构,每个拦截器都可以在请求处理程序执行之前或之后进行特定的操作。拦截器链中的拦截器按照预定义的顺序执行,每个拦截器都有机会处理请求和响应。拦截器链可以保证各个拦截器的有序执行,以达到预期的处理逻辑。
5、使用案例
5.1、创建拦截器
创建一个拦截器的包,在包下创建拦截器
package com.tgq.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;//返回true / false
}
@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...");
}
}
5.2、配置拦截器
在我们的自己配置spring-mvc.xml里面配置我们的拦截器
<mvc:interceptors> <bean class="com.tgq.interceptor.OneInterceptor"></bean> </mvc:interceptors>
5.3、运行测试
启动项目,打开浏览器访问请求地址,测试拦截器的拦截效果。
他们的执行顺序为:preHandle --> postHandle --> afterCompletion
http://localhost:8080/sc/list
5.4、拦截器工作原理
- preHandle:用于对拦截到的请求进行预处理,方法接收布尔(true,false)类型的返回值,返回true:放行,false:不放行。
执行时机:在处理器方法执行前执行
参数 | 说明 |
---|---|
request | 请求对象 |
response | 响应对象 |
handler | 拦截到的处理器方法 |
ModelAndView | 处理器方法返回的模型和视图对象,可以在方法中修改模型和视图 |
- afterCompletion:用于在整个流程完成之后进行最后的处理,如果请求流程中有异常,可以在方法中获取对象
执行时机:视图渲染完成后(整个流程结束之后)
参数 | 说明 |
---|---|
request | 请求参数 |
response | 响应对象 |
handler | 拦截到的处理器方法 |
ex | 异常对象 |
5.5、拦截器链
如果多个拦截器能够对相同的请求进行拦截,则多个拦截器会形成一个拦截器链,主要理解拦截器链中各个拦截器的执行顺序。拦截器链中多个拦截器的执行顺序,根拦截器的配置顺序有关,先配置的先执行。
package com.tgq.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...");
}
}
配置spring-mvc.xml
<mvc:interceptors>
<!--2) 多拦截器(拦截器链)-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.tgq.interceptor.OneInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/sc/**"/>
<bean class="com.tgq.interceptor.TwoInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
走一个拦截器:edithttp://localhost:8080/stu/save
走两个拦截器:列表http://localhost:8080/sc/list
5.6、登录拦截实例
5.6.1、创建拦截器
package com.tgq.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 sname = (String) request.getSession().getAttribute("sname");
if (sname == null || "".equals(sname)) {
response.sendRedirect("/page/stu/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 {
}
}
5.6.2、配置拦截器
<!--登录拦截器实例-->
<mvc:interceptors>
<bean class="com.tgq.interceptor.LoginInterceptor"></bean>
</mvc:interceptors>
5.6.3、编写控制层
package com.tgq.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(HttpServletRequest req) {
String sname = req.getParameter("sname");
HttpSession session = req.getSession();
if ("tgq".equals(sname)) {
session.setAttribute("sname", sname);
}
return "redirect:/sc/list";
}
@RequestMapping("/logout")
public String logout(HttpServletRequest req) {
req.getSession().invalidate();
return "redirect:/sc/list";
}
}
5.6.4、前端页面
<%--
Created by IntelliJ IDEA.
User: tgq
Date: 12/9/2023
Time: 下午10:03
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<form action="/login" method="post">
账号:<input name="sname">
<input type="submit">
</form>
</body>
</html>
5.6.5、测试
登入
http://localhost:8080/logouthttp://localhost:8080/login
登出
http://localhost:8080/logouthttp://localhost:8080/logout