Spring 源码解析 | Spring MVC 拦截器

788 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划>」,赢取创作大礼包,挑战创作激励金。

在项目开发过程中我们可能与这样的需求:需要拦截用户的特定浏览权限,比如对于一些公开页面不需要访问权限,对于一些特定的页面需要登录后才能访问。对于这个场景我们通常可以通过拦截器的方式来进行处理。

Spring MVC 的拦截器与 Java Servlet 的过滤器功能类型,它主要是对用户的请求进行处理,通常在权限验证,记录日志,用户登录状态判断等功能场景使用。

注: 基础环境 JDK 17 、Spring 6.x、Tomcat 10.x

拦截器的定义

通常我们可以通过实现 HandlerInterceptor 接口进行自定义拦截器的定义。

HandlerInterceptor

示例代码如下(注意我的 servlet 是 jakarta.servlet ):

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class TestInterceptor implements HandlerInterceptor {
    @Override
    public void afterCompletion(HttpServletRequest request,
								HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
		// afterCompletion方法在控制器的处理请求方法执行完成后执行,即视图渲染结束之后执行
        System.out.println(" ======= >>>>  afterCompletion 方法执行");

    }

    @Override
    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
		// postHandle方法在控制器的处理请求方法调用之后,解析视图之前执行
		System.out.println(" ======= >>>>  postHandle   方法执行");
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
		// preHandle方法在控制器的处理请求方法调用之后,解析视图之前执行
		System.out.println(" ======= >>>>  preHandle   方法执行");
        return false;
    }
}

在上述拦截器的定义中实现了 HandlerInterceptor 接口,并实现了接口中的 3 个方法。有关这 3 个方法的描述如下。

  • preHandle 方法:该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
  • postHandle 方法:该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。
  • afterCompletion 方法:该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。

拦截器的配置

让自定义的拦截器生效需要在 Spring MVC 的配置文件中进行配置,配置示例代码如下:

XML 方式

<!-- 配置拦截器 -->
<mvc:interceptors>
    <!-- 配置一个全局拦截器,拦截所有请求 -->
    <bean class="interceptor.TestInterceptor" /> 
    <mvc:interceptor>
        <!-- 配置拦截器作用的路径 -->
        <mvc:mapping path="/**" />
        <!-- 配置不需要拦截作用的路径 -->
        <mvc:exclude-mapping path="" />
        <!-- 定义<mvc:interceptor>元素中,表示匹配指定路径的请求才进行拦截 -->
        <bean class="interceptor.Interceptor1" />
    </mvc:interceptor>
    <mvc:interceptor>
        <!-- 配置拦截器作用的路径 -->
        <mvc:mapping path="/gotoTest" />
        <!-- 定义在<mvc: interceptor>元素中,表示匹配指定路径的请求才进行拦截 -->
        <bean class="interceptor.Interceptor2" />
    </mvc:interceptor>
</mvc:interceptors>

在上述示例代码中, <mvc:interceptors> 元素用于配置一组拦截器,其子元素 定义的是全局拦截器,即拦截所有的请求。 <mvc:interceptor> 元素中定义的是指定路径的拦截器,其子元素 <mvc:mapping> 用于配置拦截器作用的路径,该路径在其属性 path 中定义。如上述示例代码中,path 的属性值“/**”表示拦截所有路径,“/gotoTest”表示拦截所有以“/gotoTest”结尾的路径。如果在请求路径中包含不需要拦截的内容,可以通过 <mvc:exclude-mapping> 子元素进行配置。

需要注意的是,<mvc:interceptor> 元素的子元素必须按照 <mvc:mapping.../>、<mvc:exclude-mapping.../>、<bean.../> 的顺序配置。

注解方式

我们通过注解的方式配置如下:

/**
 * 注意:spring 6 中已经删除了 WebMvcConfigurerAdapter
 * 我们使用过程中实现 WebMvcConfigurer 接口即可
 */
@Configuration
public class TestConfiguration implements WebMvcConfigurer {

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new TestInterceptor()).addPathPatterns("/app/*");
	}

}

参考资料