SpringBoot-拦截器

374 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第29天,点击查看活动详情

SpringBoot拦截器(Interceptor)有点类似于Servlet中的过滤器,它主要用于拦截用户发送的请求并进行相应的处理。拦截器可以在项目中进行权限验证、记录请求信息的日志、判断用户是否登录等。过滤器和拦截器在功能上有部分重叠,定义也很相似,一些功能既可以通过过滤器实现,也可以通过拦截器实现,但是它们还是有区别的:

  • 拦截器是基于Java的反射机制,而过滤器是基于函数回调。
  • 拦截器的使用不依赖于Servlet容器,而过滤器依赖于Servlet容器。
  • 拦截器只能对Controller请求起作用,而过滤器则可以对几乎所有的请求(包括静态资源和文件等)起作用。
  • 拦截器可以访问请求的上下文、值栈里的对象,而过滤器不能访问。
  • 在一个请求的生命周期中,可以设置多个拦截器依次运行,而过滤器只能在容器初始化时被调用一次。
  • 拦截器可以获取Spring IoC容器中的各个Bean,而过滤器却获取不到,在拦截器中可以注入业务service,处理业务逻辑。
  • 过滤器是在请求Servlet之前拦截请求,对请求进行预处理。请求结束返回也是在Servlet处理完后再返回给前端。而拦截器是在请求处理之前进行拦截处理。

要使用Spring MVC中的拦截器,首先需要对拦截器类进行定义和配置。通常,拦截器类可以通过两种方式来定义。

  1. 通过实现HandlerInterceptor接口或继承HandlerInterceptor接口的实现类(如HandlerInterceptorAdapter)来定义拦截器。
  2. 通过实现WebRequestInterceptor接口或继承WebRequestInterceptor接口的实现类来定义拦截器,此接口专门用于处理Web请求。

在项目开发中,一个常见的需求就是打印所有的请求入参,方便以后问题的定位和接口的调试。下面我们自定义一个拦截器来处理所有的请求,并且把所有请求的URL和日志都打印出来,具体代码如下:

 package com.example.thymeleafdemo.inter;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 import org.springframework.web.servlet.HandlerInterceptor;
 import org.springframework.web.servlet.ModelAndView;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.util.Arrays;
 import java.util.Map;
 ​
 /**
  * 自定义拦截器
  */
 @Slf4j
 @Component
 public class MyHandlerInterceptor implements HandlerInterceptor {
     /**
      * 在业务代码处理之前进行参数记录
      */
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServlet
 Response response, Object handler) throws Exception {
        System.out.println("拦截器: preHandle在控制器的处理请求方法调用之后解析视图之前执行");
        String requestURI = request.getRequestURI();
        Map<String, String[]> parameterMap = request.getParameterMap();
 StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            sb.append(entry.getKey()).append("=").append(Arrays.toString
 (entry.getValue()));
            sb.append(",");
        }
        log.info("拦截器: 请求的url是:{},请求的参数是:{}",requestURI,sb.toString());
        return true;
     }
 ​
     /**
      * 在业务代码处理之后
      */
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response,
                         Object handler, ModelAndView modelAndView) throws
 Exception {
        System.out.println("拦截器: postHandle方法在控制器的处理请求方法调用之后解析视图之前执行");
     }
     @Override
     public void afterCompletion(HttpServletRequest request, HttpServlet Response response,
                            Object handler, Exception ex) throws Exception {
        System.out.println("拦截器: afterCompletion方法在控制器的处理请求方法执行完成后执行," +  "即视图渲染结束之后执行");
     }
 }

完成拦截的具体方法后,配置拦截器拦截哪些URL,放行静态资源的请求,拦截剩下的URL。

 package com.example.thymeleafdemo.inter;
 ​
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.Interceptor
 Registration;
 import org.springframework.web.servlet.config.annotation.Interceptor
 Registry;
 import org.springframework.web.servlet.config.annotation.WebMvc
 Configurer;
 ​
 @Configuration
 public class MyHandlerInterceptorConfig implements WebMvcConfigurer {
     @Autowired
     private MyHandlerInterceptor myHandlerInterceptor;
 ​
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         //注册TestInterceptor拦截器
         InterceptorRegistration registration = registry.addInterceptor
 (myHandlerInterceptor);
         //所有路径都被拦截
         registration.addPathPatterns("/**");
         //添加不拦截路径
         registration.excludePathPatterns(
                 "/**/*.html",            //HTML静态资源
                 "/**/*.js",              //JS静态资源
                 "/**/*.css"              //CSS静态资源
         );
     }
 }

启动项目,访问http://localhost:8080/addUser能看到结果页面,如图所示。同时IDEA的控制台中打印出了请求日志,至此已经成功完成了拦截器请求参数的拦截打印。

image-20220828154622091

根据以上自定义拦截器的实现代码,总结拦截器的执行步骤如下:

  1. 根据请求的URL,找到可以处理请求的处理器和所有拦截器。

  2. 按照配置顺序执行所有拦截器的preHandle()方法。如果当前拦截器的preHandle()方法返回true,则执行下一个拦截器的preHandle()方法(执行下一个拦截器)。如果当前拦截器返回false,倒序执行所有已经执行了的拦截器的afterCompletion。

  3. 如果任何一个拦截器返回false,则执行返回,不执行目标方法。

  4. 所有拦截器都返回true,则执行目标方法。

  5. 倒序执行所有拦截器的postHandle()方法。

    注:前面的步骤有任何异常都会触发倒序执行afterCompletion()方法。

  6. 页面成功渲染后,再倒序执行afterCompletion()方法。