一,JSR303的概念
JSR303是Java中的一个标准,用于验证和校验JavaBean对象的属性的合法性。它提供了一组用于定义验证规则的注解,如@NotNull、@Min、@Max等。在Spring MVC中,可以使用JSR303注解对请求参数进行校验。
1.2 为什么要使用JSR303?(更加理解)
前端不是已经校验过数据了吗?为什么我们还要做校验呢,直接用不就好了?草率了,假如说前端代码校验没写好又或者是对于会一点编程的人来说,直接绕过前端发请求(通过类似Postman这样的测试工具进行非常数据请求),把一些错误的参数传过来,你后端代码不就危险了嘛
所以我们一般都是前端一套校验,后端在一套校验,这样安全性就能够大大得到提升了。
1.3 常用的注解
在上面JSR303的概念中也指到了一些注解,以下是注解的详细概述
注解 | 说明 |
@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 |
1.4 @Validated与@Valid区别
@Validated:
- Spring提供的
- 支持分组校验
- 可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
- 由于无法加在成员属性(字段)上,所以无法单独完成级联校验,需要配合@Valid
@Valid:
- JDK提供的(标准JSR-303规范)
- 不支持分组校验
- 可以用在方法、构造函数、方法参数和成员属性(字段)上
- 可以加在成员属性(字段)上,能够独自完成级联校验
二,JSR303使用
2.1 导入pom.xml依赖
<!-- 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.2 配置校验规则
package com.Bingzy.model; import lombok.ToString; import org.hibernate.validator.constraints.NotBlank; @ToString public class Tbook { @ToString public class Clazz { @NotNull(message = "书籍编号不能为空") protected Integer bid; @NotBlank(message = "书籍名不能为空") protected String bname; @NotBlank(message = "书籍价格不能为空") protected Float price; public Clazz(String bid, String bname, Float price) { this.bid = bid; this.bname = bname; this.price = price; } public String getBid() { return bid; } public void setBid(String bid) { this.bid = bid; } public String getBname() { return bname; } public void setBname(String bname) { this.bname = bname; } public Float getPrice() { return price; } public void setPrice(Float price) { this.price = price; } @Override public String toString() { return "Clazz{" + "bid='" + bid + '\'' + ", bname='" + bname + '\'' + ", price=" + price + '}'; } } }
2.3 入门案例
在请求处理方法中,使用@Validated或@Valid注解要验证的对象,并根据BindingResult判断校验是否通过。
// 给数据添加服务端校验 @RequestMapping("/valiAdd") public String valiAdd(@Validated Tbook tbook, 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.tbookBiz.insertSelective(tbook); return "redirect:list"; } return "book/edit"; }
edit.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>博客的编辑界面</title> </head> <body> <center> 编辑界面 <form action="${pageContext.request.contextPath }/${empty b ? 'book/valiAdd' : 'book/edit'}" method="post"><br> 书籍id:<input type="text" name="bid" value="${b.bid }"><span style="color: red">${errorMap.bid}</span><br> 书籍名称:<input type="text" name="bname" value="${b.bname }"><span style="color: red">${errorMap.bname}</span><br> 书籍价格:<input type="text" name="price" value="${b.price }"><span style="color: red">${errorMap.price}</span><br> <input type="submit"> </form> </center> </body> </html>
运行结果:
点击新增或修改,进入页面后点击提交显示相对应的提示语句
三,拦截器
3.1 什么是拦截器?
Spring MVC拦截器是Spring框架中的一个组件,用于拦截请求并在请求处理之前或之后执行一些额外的操作。它可以用于实现一些通用的、与业务无关的功能,如日志记录、权限验证、异常处理、跨域请求处理等。
拦截器在请求到达DispatcherServlet之后,但在具体的Controller方法执行之前,提供了一个拦截点,可以对请求进行处理和干预。拦截器可以拦截请求、响应或会话的各个阶段,并在每个阶段执行预定义的操作。
3.2 什么是过滤器
依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等
3.3 拦截器与过滤器的区别?
- 过滤器(filter)
1.filter属于Servlet技术,只要是web工程都可以使用- 2.filter主要由于对所有请求过滤
3.filter的执行时机早于Interceptor- 拦截器(interceptor)
1.interceptor属于SpringMVC技术,必须要有SpringMVC环境才可以使用
2.interceptor通常由于对处理器Controller进行拦截
3.interceptor只能拦截dispatcherServlet处理的请求
3.4 应用场景
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
- 权限检查:如登录检测,进入处理器检测是否登录,如果没有直接返回到登录页面;
- 性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
- 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个Controller中的处理方法都需要的,我们就可以使用拦截器实现
四,拦截器快速入门
定义一个包并创建OneInterceptor类
拦截器(interceptor):
package com.Bingzy.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..."); } }
- spring-mvc.xml配置自定义拦截器
<mvc:interceptors> <bean class="com.Bingzy.interceptor.OneInterceptor"></bean> </mvc:interceptors>
在拦截器(interceptor)中的preHandle()方法中的返回值为true未拦截的情况:
在拦截器(interceptor)中的preHandle()方法中的返回值为false拦截的情况:
注意:拦截器会根据preHandle()方法返回值进行拦截判断,返回了一个
true
值。这个返回值表示该拦截器已经处理了当前的请求,并且可以继续向下传递请求。如果返回false
,则表示该拦截器不处理当前请求,请求将被终止
4.1 拦截器原理图
- preHandle:用于对拦截到的请求进行预处理,方法接收布尔(true,false)类型的返回值,返回true:放行,false:不放行。
执行时机:在处理器方法执行前执行
- 方法参数
参数 | 说明 |
request | 请求对象 |
response | 响应对象 |
handler | 拦截到的方法处理 |
- postHandle:用于对拦截到的请求进行后处理,可以在方法中对模型数据和视图进行修改
执行时机:在处理器的方法执行后,视图渲染之前
- 方法参数
参数 | 说明 |
request | 请求对象 |
response | 响应对象 |
handler | 拦截到的处理器方法 |
ModelAndView | 处理器方法返回的模型和视图对象,可以在方法中修改模型和视图 |
- afterCompletion:用于在整个流程完成之后进行最后的处理,如果请求流程中有异常,可以在方法中获取对象
执行时机:视图渲染完成后(整个流程结束之后)
- 方法参数
参数 | 说明 |
request | 请求参数 |
response | 响应对象 |
handler | 拦截到的处理器方法 |
ex | 异常对象 |
4.2 拦截器链
如果多个拦截器能够对相同的请求进行拦截,则多个拦截器会形成一个拦截器链,主要理解拦截器链中各个拦截器的执行顺序。拦截器链中多个拦截器的执行顺序,根拦截器的配置顺序有关,先配置的先执行
拦截器(interceptor)
package com.Bingzy.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.Bingzy.interceptor.OneInterceptor"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/book/**"/> <bean class="com.Bingzy.interceptor.TwoInterceptor"/> </mvc:interceptor> </mvc:interceptors>
运行展示:
五,用户登录权限案例
登入拦截器(interceptor):
package com.Bingzy.interceptor; import org.springframework.web.servlet.HandlerInterceptor; 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 bname = (String) request.getSession().getAttribute("bname"); if (bname == null || "".equals(bname)){ response.sendRedirect("/page/book/login"); return false; } return true; } }
spring-mvc.xml配置自定义登入拦截器
<mvc:interceptors> <bean class="com.Bingzy.interceptor.LoginInterceptor"></bean> </mvc:interceptors>
Controller层(web):
package com.Bingzy.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 bname = req.getParameter("bname"); HttpSession session = req.getSession(); if ("zs".equals(bname)){ session.setAttribute("bname",bname); } return "redirect:/book/list"; } @RequestMapping("/logout") public String logout(HttpServletRequest req){ req.getSession().invalidate(); return "redirect:/book/list"; } }
login.jsp:
简单的测试布局,可自行布局
测试结果:
总结:
如果用户发送URL中的后缀包含"/login"或"/logout",则表示当前请求是登录或退出操作,直接返回true表示继续处理请求,后端进行判断“uname”是否为“zs”,如果不是zs则域对象不会保存,从而到拦截器判断域对象为空则会重定向到登良页面,如果不为空就执行后端返回的字符串到视图解析器解析跳转指定页面