深入了解Spring中的JSR 303验证和拦截器
目录
提出问题:
为什么在Spring应用中使用JSR 303验证和拦截器?
在Spring应用中使用JSR 303验证和拦截器有多个重要原因:
数据验证:JSR 303验证提供了一种简单而强大的机制,用于验证输入数据的有效性。这在确保应用程序接收有效数据并处理它们之前非常重要,以防止潜在的错误或恶意输入。
代码可维护性:通过使用JSR 303验证,您可以将验证逻辑从业务逻辑中分离出来,使代码更易于维护和理解。这种分离还使得验证规则的更改和管理更加灵活。
减少冗余代码:JSR 303验证可以减少大量的重复性验证代码。通过定义验证注解和自定义验证器,您可以在整个应用程序中重复使用相同的验证规则,避免了编写相似的验证逻辑。
拦截器用于增强功能:拦截器允许您在请求处理之前或之后执行额外的逻辑。这对于实现认证、授权、日志记录、性能监控等功能非常有用。拦截器提供了一种面向切面编程的方法,可以增强应用程序的功能,而无需修改核心业务逻辑。
解耦性:使用拦截器和JSR 303验证可以实现松散耦合,允许您将不同的功能模块独立开发和维护。这有助于保持代码的模块化和可扩展性。
安全性:通过验证和拦截器,您可以确保输入数据的安全性,并在需要时执行额外的安全检查。这对于防止潜在的安全漏洞和攻击非常关键。
综上所述,使用JSR 303验证和拦截器可以提高Spring应用程序的可靠性、可维护性和安全性,同时也提供了一种优雅的方式来增强应用程序的功能。这些机制帮助开发人员更好地管理和保护应用程序的数据和功能。
第一部分: 什么是JSR 303验证?
1.1 什么是JSR 303?
JSR 303是Java EE规范中的一部分
JSR 303是Java EE(Enterprise Edition)规范中的一部分,它与数据验证和验证框架相关。以下是对这一事实的解释:
**Java EE(Enterprise Edition)**是一种Java平台的扩展,专门设计用于开发和部署大规模、分布式、事务性的企业级应用程序。Java EE提供了一整套规范和API,用于构建具有高可用性、安全性和可伸缩性的企业级应用。这些应用程序通常运行在Java EE兼容的应用服务器上。
**JSR(Java Specification Request)**是一种定义新的Java规范或修改现有规范的机制。每个JSR都由Java社区中的一个专家组(Expert Group)负责,他们共同制定和定义规范,以确保规范的标准化和一致性。
JSR 303是一个具体的JSR,它定义了Bean Validation规范。Bean Validation是一种用于在Java对象(通常是POJO,即Plain Old Java Object)级别执行数据验证的机制。这意味着您可以在应用程序中定义验证规则,并将这些规则应用于对象的字段或属性,以确保它们的数据符合预期的要求。
这里的关键是,JSR 303的目标是标准化数据验证机制,以便在Java EE应用程序中广泛使用。Java EE规范定义了整个企业应用程序的体系结构,包括持久性、Web服务、安全性等方面,而JSR 303则专注于数据验证的规范。这使得Java EE应用程序能够更容易地实现数据验证,而不需要自行开发或使用不同的验证库,从而增加了Java EE平台的一致性和互操作性。
总之,JSR 303是Java EE规范的一部分,它为Java EE应用程序提供了标准的数据验证机制,使得数据验证可以在企业级应用程序中更容易地实现和维护。这有助于提高应用程序的质量、可维护性和安全性。
为什么需要数据验证?
数据验证是软件开发中至关重要的一部分,它有多个重要的原因和作用:
保证数据的完整性和准确性:
数据验证确保输入和存储在应用程序中的数据是准确的、完整的,不包含错误或无效的信息。这有助于防止应用程序因数据不一致或损坏而出现问题。
防止恶意输入:
数据验证可以检测和拒绝不良或恶意输入。这有助于防止恶意用户或攻击者提交恶意数据,以尝试破坏应用程序的功能或获取未授权的访问。
提高用户体验:
通过及时检测和拒绝无效数据,数据验证可以提高用户体验。用户将获得及时的反馈,知道他们是否提供了正确的信息,而不必等到后续操作失败或出现错误。
减少错误和异常:
数据验证可以捕获并防止潜在的错误和异常。这有助于减少应用程序中的错误流程,提高应用程序的可靠性和稳定性。
节省成本和时间:
在应用程序中进行数据验证可以在早期识别和解决问题,从而节省了修复后期问题的成本和时间。修复和调试由于无效数据而引起的问题通常比提前验证更加复杂和昂贵。
合规性和安全性:
对于某些应用程序,如金融、医疗或法律领域的应用,合规性和安全性非常重要。数据验证有助于确保应用程序遵守相关法规和安全标准。
数据一致性:
数据验证有助于确保不同组件或模块之间的数据一致性。这对于大规模系统中的数据管理和交互尤为重要。
数据验证在软件开发中是一项关键任务,它有助于确保数据的质量、可靠性和安全性,提高了应用程序的质量和用户体验,同时也有助于遵守合规性和安全标准。数据验证应该被视为构建健壮应用程序的不可或缺的一部分。
1.2 使用JSR 303验证
导入依赖
<!-- 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>
配置校验规则
package com.liao.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@NotNull(message = "用户编号不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
private String name;
@NotBlank(message = "账号不能为空")
private String loginname;
@Length(min = 6, max = 15, message = "用户名长度是6-15位")
private String pwd;
}
编写方法校验
//给数据添加服务端校验
@RequestMapping("/valiAdd")
public String valiAdd(@Validated User user, BindingResult result, HttpServletRequest req) {
//如果服务端验证不通过,有错误 boolean
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";
}
前端页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<form action="${pageContext.request.contextPath }/${empty u ? 'users/valiAdd' : 'users/edit'}" method="post">
用户id:<input type="text" name="id" value="${u.id }"><span style="color: red">${errorMap.id}</span><br>
用户名:<input type="text" name="name" value="${u.name }"><span style="color: red">${errorMap.naem}</span><br>
账号:<input type="text" name="loginname" value="${u.loginname }"><span style="color: red">${errorMap.loginname}</span><br>
密码:<input type="text" name="pwd" value="${u.pwd }"><span style="color: red">${errorMap.pwd}</span><br>
</form>
</body>
</html>
1.3 JSR 303验证的优点
JSR 303验证具有多个优点,这些优点使其成为Java应用程序中常用的数据验证机制。以下是一些JSR 303验证的优点:
标准化:JSR 303是Java平台上的标准验证规范,被广泛接受并支持。这意味着它提供了一种通用的方法来执行数据验证,有助于提高应用程序的一致性和互操作性。
简化验证逻辑:JSR 303允许开发者使用注解来定义验证规则,而不需要编写大量的自定义验证代码。这简化了验证逻辑,使其更易于理解和维护。
减少冗余代码:通过定义验证注解和自定义验证器,JSR 303使得验证规则能够在整个应用程序中重复使用,避免了编写相似的验证逻辑,从而减少了冗余代码。
提高代码可读性:使用注解和声明性验证规则可以使验证逻辑在代码中更加清晰可读。这有助于开发人员更好地理解应用程序中的数据验证需求。
提供内置验证器:JSR 303提供了一组内置的验证器,涵盖了常见的验证场景,如验证字符串长度、范围、正则表达式匹配等。这些内置验证器可直接使用,无需自定义。
支持自定义验证规则:除了内置验证器,JSR 303还允许开发者创建自定义验证注解和验证器,以满足特定的业务需求。这使得应对复杂的验证场景变得更容易。
集成容易:JSR 303与Java EE和Spring等常用的Java框架和平台集成良好,可以轻松地与这些框架一起使用,提供了方便的验证功能。
提高安全性:通过验证输入数据的有效性,JSR 303可以防止恶意输入和潜在的安全漏洞。这有助于提高应用程序的安全性。
提供多语言支持:JSR 303允许开发者为验证错误消息提供多语言支持,以满足不同用户和地区的需求。
错误处理和反馈:JSR 303提供了丰富的错误处理和反馈机制,使开发人员能够轻松地捕获验证错误并采取适当的操作。
JSR 303验证的优点包括了标准化、简化验证逻辑、减少冗余代码、提高可读性、内置验证器、自定义验证规则、集成容易、提高安全性、多语言支持以及丰富的错误处理和反馈。这些优点有助于提高应用程序的质量、可维护性和安全性,使数据验证变得更加方便和高效。
1.4 JSR303之前所使用的验证方式有哪些?
在Java中,JSR 303之前的数据验证方式包括了一些传统的手动验证和第三方库。以下是一些在JSR 303之前常用的数据验证方式:
手动验证:
- 开发人员通常编写自定义的验证代码,使用条件语句(如if、switch)来检查输入数据的有效性。这种方式需要大量的手动编码,容易出现重复和繁琐。
Java异常:
- Java语言本身提供了异常机制,可以用于捕获和处理数据验证错误。开发人员可以通过抛出异常来表示验证失败。然后,调用代码必须捕获并处理这些异常。虽然这是一种常见的方式,但它并不适用于复杂的验证规则,因为异常通常用于处理更严重的错误情况。
Apache Commons Validator:
- Apache Commons Validator是一个常用的开源验证库,它提供了一组用于验证输入数据的工具和类。开发人员可以使用这些工具和类来定义和执行验证规则。这是一个相对简单的方式来执行基本的数据验证。
自定义验证框架:
- 一些开发人员和组织创建了自己的验证框架,以满足特定的业务需求。这些框架通常是基于自定义注解或规则引擎构建的,用于实现复杂的数据验证逻辑。
第三方验证库:
- 除了Apache Commons Validator之外,还存在其他第三方验证库,如OVal和JValidate等。这些库提供了一些验证功能,允许开发人员定义和应用验证规则,但它们的使用和社区支持相对有限。
虽然这些方法在一定程度上可以用于数据验证,但它们通常需要大量手动编码,容易出错,并且难以维护。JSR 303的引入为Java应用程序提供了一种更标准和更容易使用的数据验证机制,通过注解和标准化的验证器简化了验证过程,提高了代码的可读性和可维护性。因此,JSR 303成为了Java应用中常用的数据验证方法之一。
第二部分: 拦截器的作用和用法
2.1 什么是拦截器?
依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。
2.2 拦截器的概念
拦截器(Interceptor)是一种在软件开发中广泛使用的设计模式,用于捕获、处理和转发请求或事件,通常用于在应用程序中执行附加操作或增加功能。拦截器在不修改原始请求或事件的情况下,允许开发者在其前后插入自定义的逻辑,以实现各种用途,如日志记录、认证、授权、性能监控、异常处理等。
以下是拦截器的一些关键概念和特点:
中间层:拦截器通常处于应用程序的中间层,位于请求或事件的处理路径中。它们充当了请求处理器和底层资源之间的中介,拦截请求并在需要时执行附加任务。
链式执行:多个拦截器通常按照特定的顺序组成拦截器链(Interceptor Chain)。请求或事件依次通过这些拦截器,每个拦截器都有机会对其进行处理或修改。这种链式执行方式使得拦截器可以协同工作,完成复杂的任务。
功能增强:拦截器的主要目的是增强应用程序的功能。它们可以在请求处理前执行预处理操作,如认证和授权,也可以在请求处理后执行后处理操作,如日志记录和异常处理。这有助于将应用程序的功能与核心业务逻辑分离开来。
通用性:拦截器是一种通用的设计模式,可以应用于不同类型的应用程序,包括Web应用、桌面应用、移动应用等。不同框架和平台提供了各种拦截器的实现方式。
配置灵活性:通常,拦截器的行为和顺序可以通过配置进行调整,而无需修改应用程序的源代码。这种灵活性使得开发者可以根据需要自定义拦截器的行为。
总之,拦截器是一种强大的设计模式,用于在应用程序中实现横切关注点(Cross-Cutting Concerns),这些关注点通常跨足于不同部分的应用程序。通过拦截器,开发者可以将附加操作和功能与核心业务逻辑分离开来,从而提高了代码的模块化、可维护性和可扩展性。这使得拦截器成为开发复杂应用程序的重要工具。
2.3 拦截器应用场景
拦截器在应用程序开发中有广泛的应用场景,它们用于拦截、处理和控制请求和响应。以下是一些常见的拦截器应用场景:
身份验证和授权: 拦截器可用于验证用户的身份和权限。当用户尝试访问受保护的资源时,拦截器可以检查用户的凭证(如用户名和密码),并验证其是否具有足够的权限执行特定操作。
日志记录和监控: 拦截器可以用于记录请求和响应的详细信息,以便进行日志记录和监控。这有助于开发人员追踪应用程序的行为,诊断问题以及监测性能。
缓存和性能优化: 拦截器可以用于实现数据缓存,从而提高应用程序的性能。它们可以拦截请求,检查是否已经有缓存数据可用,如果是,就返回缓存的数据,而不需要执行昂贵的计算或数据库查询。
异常处理: 拦截器可以用于捕获和处理异常情况。例如,如果在请求处理过程中发生错误,拦截器可以捕获该错误并返回友好的错误消息给客户端,而不是让应用程序崩溃。
请求/响应转换: 拦截器可以用于对请求或响应进行转换或修改。这可以包括数据格式的转换(例如,将JSON请求转换为对象)或添加额外的头信息到响应中。
安全性增强: 拦截器可以用于加强应用程序的安全性,例如防止跨站点请求伪造(CSRF)攻击或防止SQL注入攻击。它们可以在请求进入应用程序之前执行安全性检查。
跟踪和分析: 拦截器可以用于跟踪用户行为,例如收集用户浏览的页面或使用的功能,以进行分析和统计。
缓存控制: 拦截器可以设置缓存策略,包括缓存的过期时间和条件,以控制客户端和代理服务器对响应数据的缓存行为。
总之,拦截器在应用程序中扮演着非常重要的角色,用于控制、管理和增强请求和响应的处理过程。它们有助于提高应用程序的安全性、性能、可维护性和可扩展性,并在各种场景下提供灵活的解决方案。
2.4 拦截器与过滤器的区别
拦截器(Interceptors)和过滤器(Filters)都是在应用程序中用于处理请求和响应的组件,但它们在实现方式和应用场景上有一些区别:
-
实现方式:
- 拦截器: 拦截器通常是框架(例如Spring框架)或特定应用程序服务器(例如Java EE服务器)提供的一部分。开发者可以创建自定义拦截器,并将它们配置在应用程序中。拦截器通常通过注解或配置文件来定义拦截规则,然后在请求处理过程中拦截、处理或修改请求和响应。
-
过滤器: 过滤器是Java Servlet规范的一部分,它们是基于Java编写的类,实现了
javax.servlet.Filter
接口。过滤器可以通过在web.xml
配置文件中定义过滤器名称和URL模式来启用。它们以链式的方式处理请求和响应,可以在请求进入Servlet之前执行,在响应返回客户端之前执行。
-
应用场景:
- 拦截器: 拦截器通常用于处理应用程序级别的逻辑,例如身份验证、授权、日志记录、性能监控等。它们更加关注业务逻辑,可以访问应用程序的上下文和资源。
- 过滤器: 过滤器通常用于处理底层的HTTP请求和响应,例如请求参数处理、编码解码、跨站点脚本(XSS)防护、内容压缩等。它们更接近HTTP层,不直接访问应用程序上下文。
-
执行顺序:
- 拦截器: 拦截器的执行顺序通常由框架或容器管理,可以配置多个拦截器,它们按照定义的顺序依次执行。
-
过滤器: 过滤器的执行顺序是根据它们在
web.xml
配置文件中的声明顺序来确定的,它们按照声明的顺序依次执行。
-
依赖关系:
- 拦截器: 拦截器通常依赖于框架或容器,需要特定的框架或容器来运行。
- 过滤器: 过滤器是Java Servlet规范的一部分,可以在任何支持Servlet规范的应用程序服务器中使用,不依赖于特定的框架。
总之,拦截器和过滤器都是用于处理请求和响应的重要组件,但它们的实现方式、应用场景和执行顺序略有不同。开发者应根据具体的需求和应用程序架构选择合适的组件来实现所需的功能。
2.5 拦截器的使用
用户登录权限控制
OneInterceptor:
package com.liao.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...");
}
}
TwoInterceptor:
package com.liao.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>
<bean class="com.liao.interceptor.LoginInterceptor"></bean>
</mvc:interceptors>
<mvc:interceptors>
<!--2) 多拦截器(拦截器链)-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.liao.interceptor.OneInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/mic/**"/>
<bean class="com.liao.interceptor.TwoInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
LoginInterceptor
package com.liao.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 mname = (String) request.getSession().getAttribute("mname");
if (mname == null || "".equals(mname)){
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 {
}
}
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/login" method="post" enctype="multipart/form-data">
<label>用户名称:</label><br/>
<input type="text" name="mname"/><br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
结论:
-
总结JSR 303验证和拦截器的重要性
为了进一步了解JSR 303验证和拦截器的重要性时,可以将其比喻为以下情境:
JSR 303验证就像是我们在现实生活中的规则和标准。想象一下,我们正在组织一场学校比赛,有一系列规则和标准,以确保比赛的公平性和安全性。这些规则包括比赛的最大时长、参赛者的年龄限制、安全装备的使用等等。如果没有这些规则,比赛可能会混乱,参赛者可能会受伤,结果可能不公平。JSR 303验证就像是在编程世界中定义的这些规则,用来确保数据的完整性和合法性。它们帮助我们捕获和预防错误,提高了程序的质量和安全性,就像比赛规则确保了比赛的公平性一样。
而拦截器则类似于门卫或安全系统。在我们的日常生活中,我们使用门卫和安全系统来控制谁可以进入我们的家或办公室,以及他们可以做什么。拦截器的作用就类似于这些门卫和安全系统,它们在我们的应用程序中充当守卫,确保只有经过身份验证和授权的用户能够访问特定资源或执行特定操作。这有助于保护我们的应用程序免受未经授权的访问和潜在的威胁。此外,拦截器还可以用来记录访问日志、监控活动和优化性能,就像门卫可以记录来访者的信息一样。
综合起来,JSR 303验证和拦截器就像是我们编写的规则和保安系统,它们一起确保我们的应用程序既能够正常运行,又能够受到保护,就像我们的比赛和家庭一样。这两个元素都是为了提高程序的质量、安全性和用户体验而不可或缺的。