Spring MVC 拦截器 (HandlerInterceptor) 是什么? 它与 Servlet Filter 有什么区别?
Spring MVC 拦截器 (HandlerInterceptor
) 是 Spring Web MVC 框架提供的一种机制,在请求处理的特定阶段插入自定义逻辑。它主要用于预处理和后处理 Controller (Handler) 的请求。
Spring MVC 拦截器 (HandlerInterceptor) 是什么?
HandlerInterceptor
是一个接口,它定义了三个主要的回调方法,在请求生命周期的不同点进行干预:
-
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
:- 调用时机: 在 Controller 方法 (Handler) 执行之前调用。
- 返回值:
true
: 请求会继续向下执行,传递给下一个拦截器或最终的 Handler。false
: 请求处理流程被中断,不会执行后续的拦截器和 Handler。此时,需要自己负责生成响应 (例如,通过response.getWriter().write(...)
)。
- 用途: 权限校验、日志记录、参数预处理、请求合法性检查等。
-
postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
:- 调用时机: 在 Controller 方法 (Handler) 执行之后,但在 DispatcherServlet 渲染视图之前调用。
- 参数
modelAndView
: 包含了 Handler 返回的模型数据和视图信息。我们可以在这里修改ModelAndView
。 - 注意: 如果 Handler 方法执行过程中抛出异常,则此方法不会被调用。
- 用途: 修改模型数据、添加公共数据到模型、修改视图名称、记录操作日志(此时可以获取到Handler处理结果的一些信息)。
-
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
:- 调用时机: 在整个请求处理完成之后,即 DispatcherServlet 渲染视图之后调用。通常在视图渲染完毕后执行,用于资源清理。
- 参数
ex
: 如果 Handler 执行过程中或视图渲染过程中抛出了异常,ex
会包含该异常对象,否则为null
。 - 注意: 无论 Handler 方法是否成功执行或是否抛出异常,此方法总是会被调用 (前提是
preHandle
返回true
)。 - 用途: 资源清理、异常日志记录、性能监控(记录整个请求处理时间)。
HandlerInterceptor 与 Servlet Filter 的区别:
特性 | Servlet Filter (javax.servlet.Filter ) | Spring MVC HandlerInterceptor (org.springframework.web.servlet.HandlerInterceptor ) |
---|---|---|
作用范围 | 基于 Servlet 规范,作用于 Servlet 容器级别。可以拦截所有进入 Servlet 的请求,不限于 Spring MVC。 | 仅作用于 Spring MVC 的 DispatcherServlet 处理的请求。它位于 DispatcherServlet 内部的请求处理链中。 |
依赖 | 依赖 Servlet API。 | 依赖 Spring MVC 框架。 |
调用时机 | 在 DispatcherServlet 之前(或之后,取决于 Filter Chain 的配置顺序)。 | 在 DispatcherServlet 接收到请求后,但在调用具体的 Controller (Handler) 之前、之后和完成时。 |
可获取信息 | 只能获取到原始的 HttpServletRequest 和 HttpServletResponse 。 | 可以获取到 Spring MVC 上下文中的信息,例如将要执行的 Handler (Controller 方法)、ModelAndView 等。 |
控制粒度 | 相对粗粒度,通常用于全局性的任务,如编码转换、安全过滤。 | 更加细粒度,可以针对特定的 Handler 或 URL 模式进行拦截,能更深入地参与到 Spring MVC 的请求处理流程中。 |
异常处理 | Filter 的 doFilter 方法需要自己处理异常,或向上抛出。 | postHandle 不会在 Handler 抛异常时调用,但 afterCompletion 始终会被调用,并可以获取到异常信息。 |
与Spring集成 | 可以通过 FilterRegistrationBean 在 Spring Boot 中注册,但本质上仍是 Servlet 规范的一部分。 | 是 Spring MVC 的核心组件,与 Spring IoC 容器紧密集成,可以方便地注入其他 Spring Bean。 |
简单来说:
- Filter 更底层,更通用,作用范围更广,适合做一些与具体框架无关的通用处理。
- Interceptor 更贴近 Spring MVC,更精细,能访问到更多 Spring MVC 的内部信息,适合做与 Spring MVC 请求处理流程相关的特定逻辑。
如何定义和注册拦截器?
-
定义拦截器 (实现
HandlerInterceptor
接口或继承HandlerInterceptorAdapter
(已废弃,推荐直接实现接口)):import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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;@Component // 将拦截器注册为一个Bean,如果需要在拦截器中注入其他Bean public class MyCustomInterceptor implements HandlerInterceptor {private static final Logger logger = LoggerFactory.getLogger(MyCustomInterceptor.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {logger.info("MyCustomInterceptor: PreHandle - URL: {}", request.getRequestURI());// 示例:简单的权限校验String token = request.getHeader("Authorization");if (token == null || !isValidToken(token)) {logger.warn("MyCustomInterceptor: PreHandle - Unauthorized access to {}", request.getRequestURI());response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.getWriter().write("Unauthorized - Invalid or missing token.");return false; // 中断请求}return true; // 继续执行}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {logger.info("MyCustomInterceptor: PostHandle - Handler: {}", handler.getClass().getSimpleName());if (modelAndView != null) {modelAndView.addObject("interceptorMessage", "Message added by interceptor!");logger.info("MyCustomInterceptor: PostHandle - Added data to ModelAndView for view: {}", modelAndView.getViewName());}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {logger.info("MyCustomInterceptor: AfterCompletion - Request completed.");if (ex != null) {logger.error("MyCustomInterceptor: AfterCompletion - Exception occurred: ", ex);}}private boolean isValidToken(String token) {// 实际的token校验逻辑return token.startsWith("Bearer valid-");} }
-
注册拦截器 (通过实现
WebMvcConfigurer
接口):这是在 Spring Boot 和现代 Spring MVC Java 配置中最常见的方式。
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;@Configuration public class WebMvcConfig implements WebMvcConfigurer {@Autowiredprivate MyCustomInterceptor myCustomInterceptor; // 注入自定义拦截器Bean// 如果你的拦截器没有被@Component注解,或者你想在这里new一个实例,也可以:// private final MyCustomInterceptor myCustomInterceptor = new MyCustomInterceptor();@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(myCustomInterceptor).addPathPatterns("/api/**", "/secure-area/**") // 指定拦截的URL模式 (Ant-style).excludePathPatterns("/api/public/**", "/login", "/error") // 指定排除的URL模式.order(1); // 可选,指定拦截器的执行顺序,数字越小优先级越高// 可以注册多个拦截器// registry.addInterceptor(new AnotherInterceptor())// .addPathPatterns("/admin/**")// .order(0);} }
注意:
addPathPatterns("/**")
: 拦截所有请求。excludePathPatterns(...)
: 排除某些路径不被拦截。order(int order)
: 如果有多个拦截器,可以通过order
来指定它们的执行顺序。preHandle
会按顺序执行,postHandle
和afterCompletion
会按相反的顺序执行。
拦截器可以用来做什么?
拦截器因其能够在请求处理的关键点介入,具有广泛的应用场景:
-
权限认证与授权:
- 在
preHandle
中检查用户是否登录,是否有访问特定资源的权限。如果无权限,则返回false
并设置相应的响应状态码或重定向到登录页面。
- 在
-
日志记录:
preHandle
: 记录请求的URL、参数、来源IP等。postHandle
: 记录Handler处理结果、返回的视图等。afterCompletion
: 记录整个请求的处理耗时、异常信息等。
-
性能监控:
- 在
preHandle
中记录开始时间,在afterCompletion
中计算总耗时。
- 在
-
统一添加数据到模型:
- 在
postHandle
中向ModelAndView
添加所有页面都需要用到的公共数据,如网站名称、当前用户信息等。
- 在
-
国际化与本地化:
- 根据请求参数或Session信息,在
preHandle
中设置当前的Locale
。
- 根据请求参数或Session信息,在
-
防止重复提交:
- 在
preHandle
中生成并校验Token,防止用户重复提交表单。
- 在
-
请求参数预处理或校验:
- 在
preHandle
中对请求参数进行一些预处理或基础校验。
- 在
-
请求上下文设置/清理:
- 使用
ThreadLocal
在preHandle
中设置一些请求相关的上下文信息,在afterCompletion
中清理。
- 使用
-
API版本控制:
- 根据请求头或路径信息,在
preHandle
中将请求路由到不同版本的API处理器。
- 根据请求头或路径信息,在
Spring MVC 拦截器提供了灵活的方式来对Web请求进行横切点的处理,使得Controller可以更专注于核心业务逻辑。