Skip to content

SpringMVC教程 - 14 拦截器

14.1 拦截器简介

在项目中,我们肯定会有这样的需求:

  • 有些接口或页面只有登录才能访问,没有登录则跳转到登录,或返回没有登录的错误码;
  • 需要打印每个接口的请求地址、请求参数、请求时间等信息,用于排查问题或性能优化;
  • 修改请求或响应的内容,例如添加头信息,或修改响应数据;
  • ...等等

怎么实现?你可以在每个接口中去实现,那显然是令人发指的操作,如果要修改,那不是要死人。


所以就需要一个可以统一来处理的地方,在请求真正进入 Controller 之前进行统一处理,我们可以使用 Spring MVC 拦截器(Interceptor)

其实拦截器不只是能在 Controller 方法调用之前进行处理,还可以在 Controller 方法调用之后、请求完成之后执行一些操作, SpringMVC拦截器有以下三个方法,我们可以按需进行重写,实现自己想要的操作:

方法作用时间功能举例
preHandle()Controller 方法执行前,如果返回true,则继续执行后续的拦截器和控制器方法;如果返回false,则中断执行流程;登录校验、参数检查
postHandle()Controller 方法执行后、视图渲染前修改 Model 数据
afterCompletion()整个请求结束后日志记录、资源清理

拦截器可以有多个,可以依次执行,各个拦截器方法执行的前后顺序如下:

preHandle() 方法默认返回 true,也就是不拦截。当重写 preHandle() 方法,在方法中触发返回 false 后,整个请求会被拦截,不再向后传播。后续其他拦截器的 preHandle()postHandle()afterCompletion() 都不会执行;当前拦截器的 postHandle()afterCompletion() 也不会执行;但之前已经执行过 preHandle() 的拦截器,它们的 afterCompletion() 会执行。

14.2 拦截器的基础使用

说了那么多,现在开始创建并使用拦截器。

下面创建一个拦截器,用来打印每个请求的执行时长。

1 创建拦截器

要创建一个拦截器,我们需要实现 HandlerInterceptor 接口。

java
package com.foooor.hellospringmvc.handler;

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

@Slf4j
@Component  // 注册为 Spring 组件
public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) {
        // 记录开始时间,存入 request
        request.setAttribute("startTime", System.currentTimeMillis());
        return true; // 放行
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) {
        // 获取时间
        Long start = (Long) request.getAttribute("startTime");

        // 计算耗时
        long duration = System.currentTimeMillis() - start;

        log.info("----执行耗时:{}, uri:{}, handler:{}, 耗时:{} ms", request.getMethod(), request.getRequestURI(), handler, duration);
    }
}
  • HandlerInterceptor 接口中有三个方法, 按需重写,这里重写两个方法,分别在请求开始和请求结束的时候记录时间。

2 配置拦截器

我们需要在 SpringMVC 配置文件中注册并配置拦截器。

springmvc-servlet.xml 配置文件中添加如下配置:

xml
<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 对所有请求都生效 -->
        <mvc:mapping path="/**"/>
        <ref bean="logInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  • 因为开启了组件扫描,并且在拦截器上添加了 @Component 注解,交给 Spring 来管理,所以这里使用 <ref> 标签就可以了。
  • 这里虽然配置了 /** 拦截了所有请求,但是其实是拦截不了静态资源的,因为在前面 静态资源处理 章节配置了 <mvc:resources mapping="/static/**" location="/static/" /> ,那么静态资源将会被 DispatcherServlet 拦截处理,也就到达不了拦截器了。

如果类上不添加 @Component 注解,可以使用下面的方式配置:

xml
<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 对所有请求都生效 -->
        <mvc:mapping path="/**"/>
        <bean class="com.foooor.hellospringmvc.handler.LogInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

通过上面的配置(任意一种),可以拦截所有的请求,打印请求的时长,日志如下:

----执行耗时:GET, uri:/, handler:com.foooor.hellospringmvc.controller.IndexController#index(HttpServletRequest), 耗时:1 ms

14.3 拦截规则和多个拦截器配置

上面配置的拦截器使用 /** 表示拦截的是所有请求,如果想拦截指定的请求,或者对指定的请求不拦截,可以使用如下的方式配置:

xml
<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 只对 /user/** 和 /order/** 生效 -->
        <mvc:mapping path="/user/**"/>
        <mvc:mapping path="/order/**"/>

        <!-- 排除登录和注册请求 -->
        <mvc:exclude-mapping path="/user/login"/>
        <mvc:exclude-mapping path="/user/register"/>

        <ref bean="logInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  • <mvc:mapping> 可以使用多个,配置多个拦截规则;<mvc:exclude-mapping> 可以配置多个例外规则(不拦截)。

如果有多个拦截器,那么可以同时配置:

xml
<!-- 配置拦截器 -->
<mvc:interceptors>
    <!-- 拦截1 -->
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <ref bean="interceptor1"/>
    </mvc:interceptor>

    <!-- 拦截2 -->
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <ref bean="interceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>
  • 使用 <mvc:interceptor> 配置多个拦截器即可,可以在配个拦截器中配置各自的拦截规则或例外规则。
  • 另外需要注意,拦截器的顺序就是上面配置的顺序,配置在前面的拦截器先执行
内容未完......