注在前面:本文中 拦截器 指 interceptor 这个单词,过滤器 指 filter 这个单词。
Spring MVC 是运行在 servlet 容器中的 Web 框架。在一个
中,大致会经历这么几个阶段:
请求进入阶段
Filter
DispatcherServlet
Interceptor
请求执行阶段
请求进入 controller 中指定路由的方法中。
请求执行后阶段/响应阶段
Interceptor
DispatcherServlet
Filter
其中,
Filter
API 由 servlet 容器提供;Interceptor
:HandlerInterceptor
和 WebRequestInterceptor
。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");
}
}
}
解释:
Filter
注册为 spring bean。Spring boot 应用启动后就可以扫描到了。/**
过滤所有请求。Filter
并存的话,需要指定它们执行的顺序。顺序号数字越小,执行越早。DemoFilter1
实现 jakarta.servlet.Filter
接口,就算是写一个 Filter
了。Filter
中 doFilter
是做具体事情的。HttpServletRequest
。FilterChain
的 doFilter
方法把请求转给后面的 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());
}
}
解释:
org.springframework.web.servlet.HandlerInterceptor
就可以了。preHandle
发生在 controller 中的 handler 方法执行之前。返回
true
表示请求需要被后续的拦截器或 handler 方法处理。false
表示请求已经处理完了,把它拦在这里。postHandle
发生在 controller 中的 handler 方法执行之后。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());
}
}
}
解释:
org.springframework.web.servlet.WebRequestInterceptor
就可以了。preHandle
发生在 controller 中的 handler 方法执行之前。postHandle
发生在 controller 中的 handler 方法执行之后。afterCompletion
发生在响应渲染之后。只有当 preHandle
正常结束时它才会被调用。在一个实现了 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 进去的拦截器排在前面。
HandlerInterceptor
和 WebRequestInterceptor
有什么区别?在 InterceptorRegistry
中的 addWebRequestInterceptor
方法里,把 WebRequestInterceptor
包装成了一个
WebRequestHandlerInterceptorAdapter
。而 WebRequestHandlerInterceptorAdapter
实现了
AsyncHandlerInterceptor
,所以它们之间没有本质区别,只是提供的方法签名略微不同而已。
AsyncHandlerInterceptor
?AsyncHandlerInterceptor
扩展了 HandlerInterceptor
,增加了一个 afterConcurrentHandlingStarted
方法。
这个方法主要用于异步请求的场景。当 handler 启动一个异步请求时,DispatcherServlet
会退出而不调用
postHandle
和 afterCompletion
方法,这是因为它通常为同步请求调用这些方法,而异步请求的处理结果(例如 ModelAndView
)
可能尚未准备好,并且将从另一个线程并发生成。在这种情况下,会调用 afterConcurrentHandlingStarted
方法,
允许实现类在将线程释放给 Servlet
容器之前执行任务,例如清理线程绑定的属性。
FilterChain
在向后传递 request 的时候,替换掉其中某些部分的实现。Spring session 就替换掉了 HttpServletRequest
的 session 的实现。