前言
今天来给大家继续分享有关SpringMVC的知识点,今天带大家探索SpringMVC的新领域,让我们一起来学习了解吧。
一、探索JSR303的世界
1. JSR303简介
1.1 什么是JSR303
JSR是Java Specification Requests的缩写,意思是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对象的标准注解和API。通过使用JSR 303的注解,可以对Java Bean的属性进行各种验证,例如检查字段是否为空、是否满足特定的格式、是否在指定范围内等。这样可以在数据绑定和有效性验证时,提供方便且统一的验证机制。
注:
验证数据是一项常见任务,它发生在从表示层到持久层的所有应用程序层中。通常在每一层都实现相同的验证逻辑,这既耗时又容易出错。为了避免重复这些验证,开发人员经常将验证逻辑直接捆绑到域模型中,将域类与验证代码混在一起,而验证代码实际上是关于类本身的元数据。
1.2 JSR303的重要性及使用原因
重要性:
- 数据有效性验证:JSR 303允许开发人员对Java Bean属性的值进行验证,以确保数据的合法性。通过使用预定义的验证注解,可以轻松地对属性进行验证,例如检查是否为空、是否满足指定的格式、是否在合理的范围内等。
- 统一验证规范:使用JSR 303可以定义一套统一的验证规范,比如校验器的注解列表,这样整个团队就可以遵循相同的验证规则,提高代码的一致性和可读性。
- 减少重复代码:使用JSR 303可以减少手动编写和维护的验证逻辑代码,因为验证逻辑已经由注解定义并集成到验证框架中。这样可以减少重复代码的编写,提高开发效率。
- 功能强大的验证:JSR 303提供了多种预定义的验证注解,如@NotNull、@Size、@Email等,可以满足大部分常见的验证需求。此外,它还支持自定义验证注解,可以根据具体业务需求创建特定的验证规则。
- 兼容性和可移植性:JSR 303是Java EE的一部分,并且被广泛支持和应用于各种Java框架和平台,如Spring、Hibernate等。因此,使用JSR 303可以获得良好的兼容性和可移植性,可以在不同的项目和环境中重复使用验证规则。
原因:
- 假如说前端代码校验没写好又或者是对于会一点编程的人来说,直接绕过前端发请求(通过类似Postman这样的测试工具进行非常数据请求),把一些错误的参数传过来,你后端代码不就危险了嘛。所以我们一般都是前端一套校验,后端在一套校验,这样安全性就能够大大得到提升了。
- JSR 303提供了一种简单、一致和可扩展的方式来验证Java Bean对象的数据有效性,提高开发效率和代码质量。通过使用JSR 303,可以减少手动编写验证逻辑的工作量,提高代码的可读性和可维护性。
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 |
扩展
@Validated与@Valid区别 :
@Validated:
- Spring提供的
- 支持分组校验
- 可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
- 由于无法加在成员属性(字段)上,所以无法单独完成级联校验,需要配合@Valid
@Valid:
- JDK提供的(标准JSR-303规范)
- 不支持分组校验
- 可以用在方法、构造函数、方法参数和成员属性(字段)上
- 可以加在成员属性(字段)上,能够独自完成级联校验
2. JSR303快速入门(基本使用)
2.1 导入依赖
2.2 配置效验规则
实体类属性设置
package com.yx.model; import lombok.ToString; import org.hibernate.validator.constraints.NotBlank; import javax.validation.constraints.NotNull; @ToString public class Clazz { @NotNull(message = "班级编号不能为空") // @Size(max = 100,min = 10,message = "大小必须在10至100之间"),不注解测会影响代码运行 protected Integer cid; @NotBlank(message = "班级名不能为空") protected String cname; @NotBlank(message = "班级教员老师不能为空") protected String cteacher; private String pic; public Clazz(Integer cid, String cname, String cteacher, String pic) { this.cid = cid; this.cname = cname; this.cteacher = cteacher; this.pic = pic; } public Clazz() { super(); } public Integer getCid() { return cid; } public void setCid(Integer cid) { this.cid = cid; } public String getCname() { return cname; } public void setCname(String cname) { this.cname = cname; } public String getCteacher() { return cteacher; } public void setCteacher(String cteacher) { this.cteacher = cteacher; } public String getPic() { return pic; } public void setPic(String pic) { this.pic = pic; } }
对应控制器的方法
// 给数据添加服务端校验 @RequestMapping("/valiAdd") public String valiAdd(@Validated Clazz clazz, 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.clazzBiz.insertSelective(clazz); return "redirect:list"; } return "clz/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> <form action="${pageContext.request.contextPath }/${empty c ? 'clz/valiAdd' : 'clz/edit'}" method="post"> 班级编号:<input type="text" name="cid" value="${c.cid }"><span style="color: red;">${errorMap.cid}</span><br> 班级名称:<input type="text" name="cname" value="${c.cname }"><span style="color: red;">${errorMap.cname}</span><br> 教员:<input type="text" name="cteacher" value="${c.cteacher }"><span style="color: red;">${errorMap.cteachcer}</span><br> 图片:<input type="text" name="pic" value="${c.pic }"><br> <input type="submit"> </form> </body> </html>
2.3 测试结果
二、研究拦截器奥秘(拦截器的使用)
2.1 什么是拦截器
拦截器(Interceptor)是一种常见的设计模式,在软件开发中用于拦截和处理请求、响应或方法调用的组件。拦截器可以允许开发人员在特定的时间点对请求进行预处理、后处理或进行额外的操作。
在Web开发中,拦截器通常用于拦截HTTP请求和响应,以在处理请求之前或之后执行一些操作。例如,可以使用拦截器进行身份验证、日志记录、性能监控、异常处理等。拦截器在框架和平台中广泛使用,如Java的Servlet过滤器、Spring框架的拦截器、Android框架中的拦截器等。它们提供了一种可插拔、可重用和可扩展的方式来处理各种请求和操作。
SpringMVC的处理器拦截器,类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。依赖于web框架,在实现上基于Java的反射机制,属于面向切面编(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个 controller生命周期之内可以多次调用。
2.2 拦截器与过滤器的区别
- 作用范围不同:拦截器通常是在应用程序内部的某一层中使用,在MVC框架中用于拦截请求和处理;过滤器则是在Servlet容器级别上使用,用于拦截HTTP请求和响应。
- 处理位置不同:拦截器位于特定处理程序的周围;过滤器位于请求链的前后。
- 功能不同:拦截器更多地用于对请求进行预处理和后处理,以及在请求处理过程中进行额外的操作;过滤器则更多地用于在请求和响应之间修改或转换数据。
- 实现接口不同:拦截器通常需要实现特定的接口,如Spring框架中的HandlerInterceptor接口;过滤器则需要实现javax.servlet.Filter接口。
- 导入依赖不同:拦截器通常与MVC框架或特定的请求处理框架(如Spring MVC)结合使用,因此可能依赖于这些框架的上下文和功能;过滤器则是在Servlet容器级别上执行的,与特定的框架无关。
2.3.应用场景
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
- 权限检查:如登录检测,进入处理器检测是否登录,如果没有直接返回到登录页面;
- 性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
- 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个Controller中的处理方法都需要的,我们就可以使用拦截器实现。
2.4 拦截器之快速入门
2.4.1 创建一个拦截器
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..."); } }
2.4.2 配置拦截器(在spring-mvc.xml中配置)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--1) 扫描com.zking.zf及子子孙孙包下的控制器(扫描范围过大,耗时)--> <context:component-scan base-package="com.yx"/> <!--2) 此标签默认注册DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter --> <mvc:annotation-driven /> <!--3) 创建ViewResolver视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- viewClass需要在pom中引入两个包:standard.jar and jstl.jar --> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <!--4) 单独处理图片、样式、js等资源 --> <mvc:resources location="/static/" mapping="/static/**"/> <!-- 处理文件的上传下载的问题--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 必须和用户JSP 的pageEncoding属性一致,以便正确解析表单的内容 --> <property name="defaultEncoding" value="UTF-8"></property> <!-- 文件最大大小(字节) 1024*1024*50=50M--> <property name="maxUploadSize" value="52428800"></property> <!--resolveLazily属性启用是为了推迟文件解析,以便捕获文件大小异常--> <property name="resolveLazily" value="true"/> </bean> <!-- 配置拦截器--> <mvc:interceptors> <bean class="com.yx.interceptor.OneInterceptor"></bean> </mvc:interceptors> <aop:aspectj-autoproxy/> </beans>
配置结果
如上图所示,如果不进行进一步配置则拦截器会拦截所有的请求,接下来会进测试
当我们进行发送请求时,如下图
则拦截器会进行拦截(因为前面未进行进一不配置则会拦截所有),控制台输出如下
它会先经过来拦截器进行预处理,然后做出输出;在经过控制器调用指定的方法;然后在经过拦截器进行控制台输出;接着回显到页面上,拦截器做出最后处理。
2.4.3 将拦截器类中的preHandle的返回值改为false的测试结果
发送请求时则会对该请求进行拦截,阻止方法请求的方法,导致无法访问到数据库,从而数据无法显示到页面上去,也不会进行执行后续代码操作。效果如下
页面效果
控制台输出
2.5 拦截器运行经脉(工作原理)
原理图
工作原理详细描述
- 定义拦截器:首先,需要定义一个或多个拦截器,并实现对应的拦截器接口或类。这些拦截器将包含要执行的操作的逻辑。
- 设置拦截器链:在需要使用拦截器的地方,例如MVC框架的控制器层,会存在一个拦截器链(Interceptor Chain)用于存储所有拦截器的实例。拦截器链按照一定的顺序组织,决定了拦截器的执行顺序。
- 执行拦截器逻辑:当请求到达拦截器链所在的位置时,每个拦截器将按照顺序依次执行。拦截器在请求处理的不同阶段会被调用,如请求前、请求后、视图渲染前、视图渲染后等。
- 拦截器的前置处理:每个拦截器在执行之前可以进行一些预处理操作,例如身份验证、参数验证、日志记录等。这些操作可以在请求到达目标处理程序之前进行,以确保请求的有效性和安全性。
- 处理程序执行:在拦截器链的最后一个拦截器执行后,请求将传递给目标处理程序,例如MVC框架中的控制器方法。处理程序执行特定的业务逻辑,并返回一个结果。
- 拦截器的后置处理:在处理程序执行完毕后,拦截器链会按照相反的顺序执行拦截器的后置处理操作。这些操作可以处理处理程序的返回结果、记录日志、清理资源等。
注:
拦截器能够在请求的不同阶段执行预定义的操作,并对请求进行干预和处理。拦截器的工作原理以及具体的拦截器接口和方法可能有所差异,具体取决于所使用的框架和应用程序架构。
2.6 拦截器链的秘密(讲解拦截器链)
2.6.1 再创建一个拦截器
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..."); } }
2.6.2 配置两个拦截器(在spring-mvc.xml中配置)
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.yx.interceptor.OneInterceptor"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/clz/**"/> <bean class="com.yx.interceptor.TwoInterceptor"/> </mvc:interceptor> </mvc:interceptors>
将上述代码添加到spring-mvc.xml文件中,将原有的注释掉。
2.6.3 测试结果
情况一:测试clz下的请求
页面
因为preHandle的返回值未改为false,所以能访问到界面
控制台
情况二:测试不是clz下的请求
2.6.4 注意
当我们去访问图片或者静态资源映射的时候是不经过拦截器的,
三、拦截器究极奥秘(案例模拟使用)
1. 创建案例所需拦截器
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 { } }
2.配置案例所需拦截器
<mvc:interceptors> <bean class="com.yx.interceptor.LoginInterceptor"></bean> </mvc:interceptors>
3. 创建案例所需控制器
package com.yx.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; /** * @author 君易--鑨 * @site www.yangxin.com * @company 木易 * @create 2023-09-09 15:25 */ @Controller public class LoginController { @RequestMapping("/login") public String login(HttpServletRequest req){ String uname = req.getParameter("uname"); HttpSession session = req.getSession(); if ("zs".equals(uname)){ session.setAttribute("uname",uname); } return "redirect:/clz/list"; } @RequestMapping("/logout") public String logout(HttpServletRequest req){ req.getSession().invalidate(); return "redirect:/clz/list"; } }
4. 创建模拟案例jsp页面
<%-- Created by IntelliJ IDEA. User: 86158 Date: 2023/9/13 Time: 0:15 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>
测试结果
模拟测试登陆
退出登陆
今天对新知识的探索就到这了,希望老铁能够三连夹关注支持一波