CheatSheet@xyzwps

Spring MVC 请求处理模型 / 拦截器 / 过滤器

注在前面:本文中 拦截器interceptor 这个单词,过滤器filter 这个单词。

Spring MVC 是运行在 servlet 容器中的 Web 框架。在一个

请求的处理流程

中,大致会经历这么几个阶段:

  1. 请求进入阶段

    1. 请求进入 servlet 容器支持的 Filter
    2. 请求进入 Spring MVC 的 DispatcherServlet
    3. 请求进入 Spring MVC 的 Interceptor
  2. 请求执行阶段

    请求进入 controller 中指定路由的方法中。

  3. 请求执行后阶段/响应阶段

    1. 请求离开 Spring MVC 的 Interceptor
    2. 请求离开 Spring MVC 的 DispatcherServlet
    3. 请求离开 servlet 容器支持的 Filter

其中,

  • Filter API 由 servlet 容器提供;
  • Spring MVC 提供两种 InterceptorHandlerInterceptorWebRequestInterceptor

如何编写 Filter

下面是一个 Filter 示例:

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component // 1
@WebServlet(urlPatterns = "/**") // 2
@Order(1) // 3
@Slf4j
public class DemoFilter1 implements Filter { // 4
    @Override // 5
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        if (req instanceof HttpServletRequest request) { // 6
            log.info("Request {} {} into filter 1", request.getMethod(), request.getRequestURI());
            chain.doFilter(req, res); // 7
            log.info("Request {} {} out of filter 1", request.getMethod(), request.getRequestURI());
        } else {
            throw new ServletException("Request is not an HttpServletRequest");
        }
    }
}

解释:

  1. Filter 注册为 spring bean。Spring boot 应用启动后就可以扫描到了。
  2. 指定要过滤哪些请求,这里 /** 过滤所有请求。
  3. 多个 Filter 并存的话,需要指定它们执行的顺序。顺序号数字越小,执行越早。
  4. DemoFilter1 实现 jakarta.servlet.Filter 接口,就算是写一个 Filter 了。
  5. FilterdoFilter 是做具体事情的。
  6. 我们一般只关注 HttpServletRequest
  7. 在请求之前做一些事情,之后通过 FilterChaindoFilter 方法把请求转给后面的 Filter 以及最后的 Servlet 来处理。

如何编写 HandlerInterceptor

下面是一个示例:

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Slf4j
public class DemoInterceptor1 implements HandlerInterceptor { // 1

    @Override // 2
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
        log.info("Request {} {} into interceptor 1", req.getMethod(), req.getRequestURI());
        return true;
    }

    @Override // 3
    public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView modelAndView) throws Exception {
       log.info("Request {} {} out of interceptor 1", req.getMethod(), req.getRequestURI());
    }

    @Override // 4
    public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) throws Exception {
        log.info("Request {} {} completed in interceptor 1", req.getMethod(), req.getRequestURI());
    }
}

解释:

  1. 实现 org.springframework.web.servlet.HandlerInterceptor 就可以了。
  2. preHandle 发生在 controller 中的 handler 方法执行之前。返回
    • true 表示请求需要被后续的拦截器或 handler 方法处理。
    • false 表示请求已经处理完了,把它拦在这里。
  3. postHandle 发生在 controller 中的 handler 方法执行之后。
  4. afterCompletion 发生在响应渲染之后。只有当 preHandle 返回 true 时它才会被调用。

如何编写 WebRequestInterceptor

下面是一个示例:

import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;

@Slf4j
public class DemoWebRequestInterceptor2 implements WebRequestInterceptor { // 1

    @Override // 2
    public void preHandle(@NonNull WebRequest request) throws Exception {
        if (request instanceof ServletWebRequest req) {
            log.info("Request {} {} into web request interceptor 2", req.getHttpMethod(), req.getRequest().getRequestURI());
        }
    }

    @Override // 3
    public void postHandle(@NonNull WebRequest request, ModelMap model) throws Exception {
        if (request instanceof ServletWebRequest req) {
            log.info("Request {} {} out of web request interceptor 2", req.getHttpMethod(), req.getRequest().getRequestURI());
        }
    }

    @Override // 4
    public void afterCompletion(@NonNull WebRequest request, Exception ex) throws Exception {
        if (request instanceof ServletWebRequest req) {
            log.info("Request {} {} COMPLETED in web request interceptor 2", req.getHttpMethod(), req.getRequest().getRequestURI());
        }
    }
}

解释:

  1. 实现 org.springframework.web.servlet.WebRequestInterceptor 就可以了。
  2. preHandle 发生在 controller 中的 handler 方法执行之前。
  3. postHandle 发生在 controller 中的 handler 方法执行之后。
  4. afterCompletion 发生在响应渲染之后。只有当 preHandle 正常结束时它才会被调用。

如何注册拦截器到 Spring MVC 中?

在一个实现了 WebMvcConfigurer 接口的配置类中,通过 addInterceptors 方法来注册拦截器。 这个配置类可以是启动类。比如:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class App implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DemoInterceptor2());
        registry.addWebRequestInterceptor(new DemoWebRequestInterceptor2());
        registry.addInterceptor(new DemoInterceptor1());
        registry.addWebRequestInterceptor(new DemoWebRequestInterceptor1());
    }

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

先被 add 进去的拦截器排在前面。

HandlerInterceptorWebRequestInterceptor 有什么区别?

InterceptorRegistry 中的 addWebRequestInterceptor 方法里,把 WebRequestInterceptor 包装成了一个 WebRequestHandlerInterceptorAdapter。而 WebRequestHandlerInterceptorAdapter 实现了 AsyncHandlerInterceptor,所以它们之间没有本质区别,只是提供的方法签名略微不同而已。

什么是 AsyncHandlerInterceptor

AsyncHandlerInterceptor 扩展了 HandlerInterceptor,增加了一个 afterConcurrentHandlingStarted 方法。 这个方法主要用于异步请求的场景。当 handler 启动一个异步请求时,DispatcherServlet 会退出而不调用 postHandleafterCompletion 方法,这是因为它通常为同步请求调用这些方法,而异步请求的处理结果(例如 ModelAndView) 可能尚未准备好,并且将从另一个线程并发生成。在这种情况下,会调用 afterConcurrentHandlingStarted 方法, 允许实现类在将线程释放给 Servlet 容器之前执行任务,例如清理线程绑定的属性。

拦截器和过滤器的常见用途由哪些?

  • 记录请求信息。比如:记录请求内容、处理时长等。
  • 做身份认证(authentication)和访问控制(authorization)。Spring security 就是这么做的。
  • FilterChain 在向后传递 request 的时候,替换掉其中某些部分的实现。Spring session 就替换掉了 HttpServletRequest 的 session 的实现。

示例

戳这里查看示例代码