目录
前言
拦截器
什么是拦截器?
拦截器的利用
自定义拦截器
注册并配置拦截器
拦截器详解
拦截路径
拦截器实行流程
适配器模式
统一数据返回格式
优点
统一异常处置惩罚
前言
在前面中,我们已经学习了spring中的一些常用操纵,那么本篇,我们就继续往下深入学习。
我们在做一些小项目的时间,假如我们想要判定用户是否已经登录,按照我们前面的学习,我们就需要用到seesion来举行判定。设想一下,我们有几个界面,而这几个界面都需要在用户登录后才能举行检察的,就想淘宝,如果我们未登录,那么他就跳转到登录界面。
对于这样的操纵,我们的界面不止一个,那么我们对应的在每个页面调用后端的API,此中的方法每次都需要判定用户是否登录,这样会让代码冗余,以是,在Spring中,给我们提供了一种功能,能够让我们将这些重复的代码举行抽取——拦截器。
拦截器
什么是拦截器?
拦截器(Interceptor)是一种在哀求处置惩罚流程中,对哀求和响应举行拦截和预处置惩罚的机制。它允许开发者在哀求到达目标处置惩罚器(如控制器方法)之前或之后实行统一的逻辑,从而实现诸如权限校验、日记纪录、性能监控、哀求过滤等功能。
拦截器的利用
拦截器的利用步骤分为两步:
自定义拦截器
在Spring MVC框架中,拦截器通过实现 HandlerInterceptor 接口来定义拦截逻辑。
- package com.example.demo.interceptor;
- import com.example.demo.Result.Results;
- import com.example.demo.constant.Constants;
- import com.fasterxml.jackson.databind.ObjectMapper;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
- import jakarta.servlet.http.HttpSession;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.http.HttpStatus;
- import org.springframework.stereotype.Component;
- import org.springframework.web.servlet.HandlerInterceptor;
- import org.springframework.web.servlet.ModelAndView;
- /**
- * 登录拦截器,用于在请求处理前验证用户是否已登录
- */
- @Slf4j
- @Component
- public class LoginInterceptor implements HandlerInterceptor {
- @Autowired
- private ObjectMapper objectMapper;
-
- /**
- * 在请求处理之前进行拦截
- *
- * @param request HttpServletRequest对象,用于获取请求信息
- * @param response HttpServletResponse对象,用于设置响应信息
- * @param handler 请求处理器,可以是HandlerMethod或RequestMappingHandler等
- * @return boolean 表示是否继续执行其他拦截器和目标方法。返回true表示继续执行,返回false表示中断执行。
- * @throws Exception 抛出异常表示拦截器处理出现错误
- */
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- log.info("方法执行前");
- //进行登录校验
- HttpSession session= request.getSession(false);
- if(session!=null&&"true".equals(session.getAttribute(Constants.USER_SESSION_KEY))){
- // 用户已登录,继续执行请求
- return true;
- }
- // 用户未登录,返回未授权错误信息
- Results results=Results.unLogin();
- response.setStatus(HttpStatus.UNAUTHORIZED.value());
- response.getOutputStream().write(objectMapper.writeValueAsString(results).getBytes());
- response.setContentType("application/json;charset=UTF-8");
- response.getOutputStream().close();
- return false;
- }
- /**
- * 在请求处理之后,视图渲染之前进行拦截
- *
- * @param request HttpServletRequest对象,用于获取请求信息
- * @param response HttpServletResponse对象,用于设置响应信息
- * @param handler 请求处理器,可以是HandlerMethod或RequestMappingHandler等
- * @param modelAndView ModelAndView对象,用于添加模型数据或修改视图
- * @throws Exception 抛出异常表示拦截器处理出现错误
- */
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
- log.info("方法执行后");
- HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
- }
- /**
- * 在请求完成之后执行的方法
- *
- * @param request 传入的HTTP请求对象,包含请求相关数据
- * @param response 传入的HTTP响应对象,包含响应相关数据
- * @param handler 处理请求的处理器对象,可以是任何类型的对象
- * @param ex 请求处理过程中发生的异常,如果没有异常,则为null
- * @throws Exception 根据具体实现可能会抛出的异常
- *
- * 此方法主要用于在请求处理完成后进行一些清理工作,例如关闭数据库连接、释放资源等
- * 它是在请求处理的最后一步调用的,确保了所有处理逻辑已经执行完毕
- */
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
- HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
- }
- }
复制代码
- preHandler():该方法是在目标方法实行前实行的。若返回true,则继续实行后续的业务逻辑,返回false,则中断后续的操纵。
- postHandler():该方法是在目标方法实行后再实行的。
- afterCompletion():该方法是在视图渲染之后实行的,在postHandler()方法之后实行,但由于现在前后端分离,以是后端根本上接触不到视图的渲染,这个方法用的少。
注册并配置拦截器
注册配置拦截器我们需要实现 WebMvcConfiguer 接口,并实现此中的 addInterceptors 方法。
- package com.example.demo.config;
- import com.example.demo.interceptor.LoginInterceptor;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
- /**
- * 配置类用于配置Web相关的设置
- */
- @Configuration
- public class WebConfig implements WebMvcConfigurer {
- /**
- * 登录拦截器,用于拦截请求以进行登录验证
- */
- @Autowired
- private LoginInterceptor loginInterceptor;
- /**
- * 添加拦截器以配置请求的预处理和后处理
- *
- * @param registry 拦截器注册对象,用于注册自定义拦截器
- */
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- //注册拦截器
- registry.addInterceptor(loginInterceptor)
- //拦截以"/book/"开头的所有请求
- .addPathPatterns("/book/**")
- //排除"/user/login"请求,使其不被拦截
- .excludePathPatterns("/user/login");
- }
- }
复制代码 假如此时我们未登录,调用一下查询功能:
可以看到,会对哀求举行拦截。
知道了拦截器的是怎样利用的,那么就来进一步了解拦截器。
拦截器详解
拦截器的利用细节我们讲以下两个部门:
拦截路径
拦截路径指的是我们定义的拦截器对哪些哀求生效,我们在注册配置拦截器的时间,通过 addPathPatterns() 方法就可以来指定要拦截哪些哀求,也可以通过 excludePathPatterns() 方法来指定哪些哀求不需要拦截。
关于拦截路径设置的规则,有以下几种:
拦截路径 | 含义 | 举例 | /*
| 一级路径 | 能匹配/user,/book,但不能匹配/book/getList等
| /** | 恣意级路径 | 能匹配/user,/user/login,即恣意路径都可以匹配 | /book/* | /book下的一级路径 | 能匹配/book/addBook,不能匹配/book/addBook/get,/book | /book/** | /book下的恣意级路径 | 能匹配/book,/book/addBook,/book/addBook/2,不能匹配/user/login | 上面的这些拦截规则可以拦截项目中的URL,包括静态文件(如图片文件、JS和CSS等文件).
如果我们利用下面这种拦截规则,就会将前端界面的哀求也给拦截住。

 我们可以通过设置前端界面不拦截:
- package com.example.demo.config;
- import com.example.demo.interceptor.LoginInterceptor;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- /**
- * 配置类用于配置Web相关的设置
- */
- @Configuration
- public class WebConfig implements WebMvcConfigurer {
- /**
- * 登录拦截器,用于拦截请求以进行登录验证
- */
- @Autowired
- private LoginInterceptor loginInterceptor;
-
- public List<String> excludePath= Arrays.asList("/user/*",
- "/css/**",
- "/js/**",
- "/img/**",
- "/**/*.html");//放行路径
- /**
- * 添加拦截器以配置请求的预处理和后处理
- *
- * @param registry 拦截器注册对象,用于注册自定义拦截器
- */
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- //注册拦截器
- registry.addInterceptor(loginInterceptor)
- //拦截以"/book/"开头的所有请求
- .addPathPatterns("/**")
- //排除"/user/login"请求,使其不被拦截
- .excludePathPatterns(excludePath);
- }
- }
复制代码
这样,就可以获取到前端界面。
拦截器实行流程
在没有添加拦截器之前,我们调用顺序是:
在添加拦截器后:
- 在添加完拦截器后,实行Controller中的方法之前,哀求会先被拦截器拦截住,实行preHandler() 方法,这个方法会返回一个布尔范例的值。如果返回true,说明要放行,继续访问controller中的方法;如果返回false,则不会放行(controller中的方法不会实行).
- controller中的方法实行完后,会继续实行 postHandler() 方法以及 afterCompletion() 方法,实行完毕后,终极给浏览器响应数据。
我们通过观察日记,可以看到当Tomcat在启动之后,会有核心的类 DispatcherServlet 来控制程序的实行顺序。
全部的哀求都会先进入到DispatcherServlet 中,实行 doDispatch() 调度方法,如果有拦截器,就会先实行拦截器中 preHandle() 方法中的代码,如果 preHandle() 返回true,那么就会继续访问controller中的方法,当controller中的方法实行完毕,就会再回过来实行 postHandle() 和 afterCpmpletion() 方法,返回给DispatcherServlet,终极给浏览器响应数据。
我们观察 DispatcherServlet 中的源码,就可以看到这三个方法的实行流程:
在 DispatcherServlet 源码中,我还可以看到利用了适配器模式:
适配器模式
适配器模式也叫包装器模式。将一个类的接口,转换成客户期望的另有一个接口,适配器让本来接口不兼容的类可以合作无间。
简单来说:就是目标类不能直接利用,通过一个新类举行包装,适配调用方利用,把两个不兼容的接口通过一定的方式使之兼容。
利用适配器来使两个接口兼容:
实在在我们前面学习Spring日记的时间,此中的 slf4j 就利用到了适配器模式,我们想要打印日记的时间,只需要调用slf4j的API,而它会自动去调用底层的 log4j 大概 logback 来打印日记。
示例:
- package com.example.demo.config;
- /**
- * Slf4j接口定义了打印日志的方法
- */
- public interface Slf4j {
- /**
- * 打印日志信息
- * @param message 需要打印的日志信息
- */
- void print(String message);
- }
- /**
- * Log4j类提供了具体的日志打印实现
- */
- class Log4j{
- /**
- * 打印日志信息
- * @param message 需要打印的日志信息
- */
- void log4jPrint(String message){
- System.out.println("Log4j: "+message);
- }
- }
- /**
- * Slf4jAdapter类是Slf4j接口与Log4j类之间的适配器
- * 它使得Log4j可以通过Slf4j接口来打印日志
- */
- class Slf4jAdapter implements Slf4j{
-
- /**
- * log4j实例用于实际的日志打印
- */
- private Log4j log4j;
-
- /**
- * 构造函数,接收一个Log4j实例
- * @param log4j Log4j实例,用于实际的日志打印
- */
- public Slf4jAdapter(Log4j log4j) {
- this.log4j = log4j;
- }
-
- /**
- * 实现Slf4j接口的print方法,通过Log4j实例来打印日志
- * @param message 需要打印的日志信息
- */
- @Override
- public void print(String message) {
- log4j.log4jPrint(message);
- }
- }
- /**
- * Demo类包含主程序,用于演示Slf4jAdapter的使用
- */
- class Demo{
- /**
- * 主函数,创建Log4j实例并通过Slf4jAdapter适配,然后打印日志信息
- * @param args 命令行参数
- */
- public static void main(String[] args) {
- Log4j log4j = new Log4j();
- Slf4j slf4j = new Slf4jAdapter(log4j);
- slf4j.print("Hello World");
- }
- }
复制代码 可以看到,我们不需要修改log4j的api,只需要利用适配器,就可以更换日记框架,维护系统。
那么为什么不直接调用Log4j呢?
适配器模式实在可以看做一种“赔偿模式”,用来调停计划上的缺陷,利用这种模式是无奈之举。如果在计划初期,能够规避接口不兼容的问题,那么就不需要利用适配器模式了。
统一数据返回格式
我们在做项目的时间,如果前端调用后端返回的数据格式都差别,后序如果修改起来,就显得有点杂乱,以是我们可以对返回的数据格式举行统一——统一数据返回格式。
在SpringBoot,如果我们想要在返回数据响应之前对数据举行一些逻辑操纵,那么我们就需要利用到注解 @ControllerAdvice 和 ResponseBodyAdvice 接口的实现。
定义下存常量的类:
- package com.example.demo.constant;
- public class Constants {
- public static final String USER_SESSION_KEY = "user";
- public static final Integer SUCCESS_CODE = 200;//成功
- public static final Integer FAIL_CODE = -2;//失败
- public static final Integer UNLOGIN_CODE = -1;//未登录
- }
复制代码 统一返回数据格式:
- package com.example.demo.Result;
- import com.example.demo.constant.Constants;
- import lombok.Data;
- @Data
- public class Results<T> {
- private Integer code;//200成功,-1未登录,-2程序异常
- private String msg;
- private T data;
- public static <T> Results success(T data){
- Results results=new Results();
- results.setCode(Constants.SUCCESS_CODE);
- results.setMsg("");
- results.setData(data);
- return results;
- }
- public static Results unLogin(){
- Results results=new Results();
- results.setCode(Constants.UNLOGIN_CODE);
- results.setMsg("用户未登录");
- return results;
- }
- public static <T> Results fail(String msg){
- Results results=new Results();
- results.setCode(Constants.FAIL_CODE);
- results.setMsg(msg);
- return results;
- }
- public static <T> Results fail(){
- Results results=new Results();
- results.setCode(Constants.FAIL_CODE);
- results.setMsg("程序出现异常");
- return results;
- }
- }
复制代码 响应处置惩罚:
- package com.example.demo.config;
- import com.example.demo.Result.Results;
- import com.fasterxml.jackson.databind.ObjectMapper;
- import lombok.SneakyThrows;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.core.MethodParameter;
- import org.springframework.http.MediaType;
- import org.springframework.http.server.ServerHttpRequest;
- import org.springframework.http.server.ServerHttpResponse;
- import org.springframework.web.bind.annotation.ControllerAdvice;
- import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
- /**
- * 全局响应处理,统一处理所有响应
- * 该类实现了ResponseBodyAdvice接口,用于自定义响应体
- */
- @ControllerAdvice
- public class ResponseAdvice implements ResponseBodyAdvice {
- @Autowired
- private ObjectMapper objectMapper;
- /**
- * 判断是否支持当前的返回类型和转换器类型
- * 该方法始终返回true,表示支持所有类型的响应处理
- */
- @Override
- public boolean supports(MethodParameter returnType, Class converterType) {
- return true;
- }
- /**
- * 在写入响应体前处理数据
- * 该方法根据返回的数据类型,进行相应的处理和封装
- * 如果返回类型是String,则将其作为成功消息封装进Results对象
- * 如果返回类型已经是Results,则直接返回
- * 否则,将返回值作为成功数据封装进Results对象
- */
- @SneakyThrows
- @Override
- public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
- if(body instanceof String){
- return objectMapper.writeValueAsString(Results.success(body));
- }
- if(body instanceof Results){
- return body;
- }
- return Results.success(body);
- }
- }
复制代码 调用一下看看:


可以看到,这样的话,如果前端想要获取到我们后端的数据,以及后续修改操纵等,就更加清晰容易操纵了。
我们再来看一处地方:

这里为什么要这样写呢?
SpringMVC默认会注册一些自导的HttpMessageConverter(从先后顺序排序分别为ByteArrayHttpMessageConverter、StringHttpMessageConverter、SourceHttpMessageConverte、SourceHttpMessageConverterr、AllEncompassingFormHttpMessageConverter)

而此中的 AllEncompassingFormHttpMessageConverter 会根据项目依靠环境添加对应的HttpMessageConverter。

在依靠中引⼊jackson包后,容器会把 MappingJackson2HttpMessageConverter ⾃动注册到
messageConverters 链的末尾。Spring会根据返回的数据范例,从 messageConverters 链选择合适的 HttpMessageConverter。当返回的数据是⾮字符串时,使⽤的MappingJackson2HttpMessageConverter 写⼊返回对象。当返回的数据是字符串时, StringHttpMessageConverter 会先被遍历到,这时会以为
StringHttpMessageConverter 可以利用。
可以看到子类StringHttpMessageConverter中的addDefaultHeaders()方法接收的参数为String,但我们需要返回的范例为Results范例,以是这里我们就需要利用SpringBoot内置提供的Jackson来实现信息的序列化。即:
- @SneakyThrows是 Lombok 提供的一个注解,用于简化 Java 中的异常处置惩罚。它允许开发者在方法中抛出受检异常(checked exceptions),而无需在方法签名中显式声明 throws,也不需要利用 try-catch块。
优点
- ⽅便前端程序员更好的接收息争析后端数据接口返回的数据
- 低沉前端程序员和后端程序员的沟通本钱,按照某个格式实现就可以了,由于全部接口都是这样返回的
- 有利于项目统⼀数据的维护和修改
- 有利于后端技术部⻔的统⼀规范的标准制定,不会出现特别古怪的返回内容
统一异常处置惩罚
当我们的程序出现异常时,我们也可以对这些异常举行统一处置惩罚。
统一异常处置惩罚利用的是 @ControllerAdvice 和 @ExceptionHandler 来实现的。
- @ControllerAdvice 表⽰控制器关照类
- @ExceptionHandler 是异常处置惩罚器,两个联合表示当出现异常的时间实行某个关照,也就是执⾏某个⽅法变乱
- package com.example.demo.config;
- import cn.hutool.core.io.resource.NoResourceException;
- import com.example.demo.Result.Results;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.http.HttpStatus;
- import org.springframework.web.bind.annotation.ControllerAdvice;
- import org.springframework.web.bind.annotation.ExceptionHandler;
- import org.springframework.web.bind.annotation.ResponseBody;
- import org.springframework.web.bind.annotation.ResponseStatus;
- import org.springframework.web.servlet.resource.NoResourceFoundException;
- /**
- * 全局异常处理类
- * 用于统一处理项目中的异常,提高代码的健壮性和用户体验
- */
- @ControllerAdvice
- @Slf4j
- @ResponseBody
- public class ErrorAdviceHandler {
- /**
- * 处理通用异常
- * @param e 异常对象
- * @return 返回处理结果
- */
- @ExceptionHandler
- public Object handler(Exception e){
- log.error("异常信息:{}",e.getMessage());
- return Results.fail(e.getMessage());
- }
- /**
- * 处理数组越界异常
- * @param e 异常对象
- * @return 返回处理结果
- */
- @ExceptionHandler
- public Object handler(ArrayIndexOutOfBoundsException e){
- log.error("异常信息:{}",e.getMessage());
- return Results.fail(e.getMessage());
- }
- /**
- * 处理空指针异常
- * @param e 异常对象
- * @return 返回处理结果
- */
- @ResponseStatus
- @ExceptionHandler
- public Object handler(NullPointerException e){
- log.error("异常信息:{}",e.getMessage());
- return Results.fail(e.getMessage());
- }
- /**
- * 处理算数异常
- * @param e 异常对象
- * @return 返回处理结果
- */
- @ExceptionHandler
- public Object handler(ArithmeticException e){
- log.error("异常信息:{}",e.getMessage());
- return Results.fail(e.getMessage());
- }
- /**
- * 处理资源未找到异常
- * @param e 异常对象
- * @return 返回处理结果
- */
- @ResponseStatus(HttpStatus.NOT_FOUND)
- @ExceptionHandler
- public Object handler(NoResourceFoundException e){
- log.error("异常信息:{} path:{}",e.getDetailMessageCode(),e.getResourcePath());
- return Results.fail(e.getMessage());
- }
- }
复制代码 测试案例:
- package com.example.demo.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- /**
- * Demo控制器类,用于处理演示项目的HTTP请求
- */
- @RestController
- @RequestMapping("/demo")
- public class DemoController {
-
- /**
- * 处理/t1请求的方法
- * 该方法演示了未处理的除零异常,用于教学或测试目的
- *
- * @return 成功信息字符串,但在执行中将引发除零异常
- */
- @RequestMapping("/t1")
- public String t1(){
- // 该行代码将产生除零异常
- int a=1/0;
- return "success";
- }
-
- /**
- * 处理/t2请求的方法
- * 该方法演示了未处理的空指针异常,用于教学或测试目的
- *
- * @return 成功信息字符串,但在执行中将引发空指针异常
- */
- @RequestMapping("/t2")
- public String t2(){
- // 该行代码将产生空指针异常
- String str=null;
- str.length();
- return "success";
- }
-
- /**
- * 处理/t3请求的方法
- * 该方法演示了未处理的数组越界异常,用于教学或测试目的
- *
- * @return 成功信息字符串,但在执行中将引发数组越界异常
- */
- @RequestMapping("/t3")
- public String t3(){
- // 初始化一个包含三个元素的数组
- int[] arr={1,2,3};
- // 该行代码将产生数组越界异常
- arr[3]=4;
- return "success";
- }
- }
复制代码
此外,如果发生空指针异常大概其他异常,异常处置惩罚中有Exception和NullPointerException的话,优先实行具体的异常,即实行NullPointerException异常。
具体异常优先于通用异常
以上就是本篇全部内容~
如有不敷,欢迎指正~
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |