spring mvc的HandlerInterceptor的原理以及使用场景

近期在面试的时候问到了 spring mvc 的 HandlerInterceptor,用过但是没深入,记录一下。

以下代码为 spring boot 2.7.15 中自带的 spring 5.3.29

如下为 DispatcherServlet 中的 doDispatch()

java 复制代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // 确定当前请求的处理器 HandlerExecutionChain
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // 确定当前请求的处理适配器 HandlerAdapter,对应实现类为 RequestMappingHandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            String method = request.getMethod();
            boolean isGet = HttpMethod.GET.matches(method);
            if (isGet || HttpMethod.HEAD.matches(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            // 针对定义的 HandlerInterceptor 重写 preHandle() 处理对应的逻辑
            // 执行时机:HandlerExecutionChain 对象中在进入 handler 之前按序处理,如果任何一个 preHandle() 返回 false,则终止处理
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // 实际调用 handler
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            // 针对定义的 HandlerInterceptor 重写 postHandle() 处理对应的逻辑,如果有多个,逆序处理
            // 执行时机:HandlerExecutionChain 对象中在进入 handler 之后,渲染视图之前进行处理
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        // 针对定义的 HandlerInterceptor 重写 afterCompletion() 处理对应的逻辑,如果有多个,将执行完 preHandle() 后最后一个返回值为 true 的下标进行逆序处理
        // 执行时机:HandlerExecutionChain 对象中在进入 handler 之后,渲染视图接触后进行处理
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

其中通过 HandlerExecutionChain、HttpServletRequest、HttpServletResponse、HandlerAdapter、HandlerExecutionChain 来进行请求处理。

处理请求的是 HandlerAdapter 的实现类 RequestMappingHandlerAdapter。这是主要的请求流程。

HandlerExecutionChain 负责处理 HandlerInterceptor 的处理过程,作为辅助的功能。没有这个请求照样进行,但是没有 HandlerAdapter 就不行了。

HandlerInterceptor 是一个接口,其中有三个方法

java 复制代码
package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;

public interface HandlerInterceptor {

	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}

}

从 DispatcherServlet#doDispatch() 的代码逻辑可知,HandlerInterceptor 的执行顺序为

java 复制代码
preHandle()
↓
postHandle()
↓
afterCompletion()

preHandle()

针对定义的 HandlerInterceptor 重写 preHandle() 处理对应的逻辑。

执行时机:HandlerExecutionChain 对象中在进入 handler 之前按序处理,如果任何一个 preHandle() 返回 false,则终止处理。

postHandle()

针对定义的 HandlerInterceptor 重写 postHandle() 处理对应的逻辑,如果有多个,逆序处理。

执行时机:HandlerExecutionChain 对象中在进入 handler 之后,渲染视图之前进行处理。

即 postHandle() 要执行,则 preHandle() 的返回值必须为 true。

afterCompletion()

针对定义的 HandlerInterceptor 重写 afterCompletion() 处理对应的逻辑,如果有多个,将执行完 preHandle() 后最后一个返回值为 true 的下标进行逆序处理。

执行时机:HandlerExecutionChain 对象中在进入 handler 之后,渲染视图接触后进行处理。

HandlerInterceptor 的三个方法具体执行逻辑在 HandlerExecutionChain 中。

java 复制代码
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	for (int i = 0; i < this.interceptorList.size(); i++) {
		HandlerInterceptor interceptor = this.interceptorList.get(i);
		if (!interceptor.preHandle(request, response, this.handler)) {
			triggerAfterCompletion(request, response, null);
			return false;
		}
		this.interceptorIndex = i;
	}
	return true;
}

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
		throws Exception {

	for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
		HandlerInterceptor interceptor = this.interceptorList.get(i);
		interceptor.postHandle(request, response, this.handler, mv);
	}
}

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
	for (int i = this.interceptorIndex; i >= 0; i--) {
		HandlerInterceptor interceptor = this.interceptorList.get(i);
		try {
			interceptor.afterCompletion(request, response, this.handler, ex);
		}
		catch (Throwable ex2) {
			logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
		}
	}
}

通过 postHandle() 和 afterCompletion() 的处理顺序让我想到了责任链模式。

看到这个想到了 servlet 的 Filter,拦截器能做的 Filter 也能做,并且不局限于 spring mvc,拦截器是 spring mvc 专有的功能,但是 servlet 是一个规范,只要一个服务对外暴露服务,使用了 servlet 容器处理这些服务(没有使用响应式处理)就可以。

Filter 的优先级比拦截器要高。

java 复制代码
package javax.servlet;

import java.io.IOException;

public interface Filter {

    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException;

    default void destroy() {
    }
}

使用场景

权限校验

如果用户未登录,则在 preHandle() 方法处理后返回 false,进行登录处理。

性能监控

统计请求的耗时,在 preHandle() 和 afterCompletion() 中添加统计相关的处理逻辑。

日志记录

在 preHandle() 记录请求方式、请求参数等。

其中监控和日志可以通过 aop 来实现。

之前自己写的文章

https://blog.csdn.net/zlpzlpzyd/article/details/133499304

参考链接

https://www.zhihu.com/question/39510340/answer/3054411211

https://blog.csdn.net/cristianoxm/article/details/123215237

https://www.cnblogs.com/seanstudy/p/15848259.html

相关推荐
Miketutu8 小时前
Spring MVC消息转换器
java·spring
小小虫码10 小时前
项目中用的网关Gateway及SpringCloud
spring·spring cloud·gateway
带刺的坐椅15 小时前
无耳科技 Solon v3.0.7 发布(2025农历新年版)
java·spring·mvc·solon·aop
精通HelloWorld!18 小时前
使用HttpClient和HttpRequest发送HTTP请求
java·spring boot·网络协议·spring·http
LUCIAZZZ19 小时前
基于Docker以KRaft模式快速部署Kafka
java·运维·spring·docker·容器·kafka
拾忆,想起19 小时前
如何选择Spring AOP的动态代理?JDK与CGLIB的适用场景
spring boot·后端·spring·spring cloud·微服务
鱼骨不是鱼翅21 小时前
Spring Web MVC基础第一篇
前端·spring·mvc
hong_zc1 天前
Spring MVC (三) —— 实战演练
java·spring·mvc
Future_yzx1 天前
Spring AOP 入门教程:基础概念与实现
java·开发语言·spring
安清h1 天前
【基于SprintBoot+Mybatis+Mysql】电脑商城项目之用户注册
数据库·后端·mysql·spring·mybatis