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

相关推荐
llwszx3 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
麦兜*7 小时前
Spring Boot启动优化7板斧(延迟初始化、组件扫描精准打击、JVM参数调优):砍掉70%启动时间的魔鬼实践
java·jvm·spring boot·后端·spring·spring cloud·系统架构
CHENWENFEIc10 小时前
SpringBoot论坛系统安全测试实战报告
spring boot·后端·程序人生·spring·系统安全·安全测试
高兴达10 小时前
RPC--Netty客户端实现
java·spring·rpc
要开心吖ZSH14 小时前
《Spring 中上下文传递的那些事儿》Part 4:分布式链路追踪 —— Sleuth + Zipkin 实践
java·分布式·spring
考虑考虑14 小时前
Springboot3.4.x中的@Bean使用
spring boot·后端·spring
萧曵 丶17 小时前
Spring @TransactionalEventListener
java·数据库·spring·事务·transactional·异步
默默coding的程序猿18 小时前
3.前端和后端参数不一致,后端接不到数据的解决方案
java·前端·spring·ssm·springboot·idea·springcloud
Kyrie_Li19 小时前
(十五)Spring Test
java·后端·spring
RainbowSea20 小时前
补充:问题:CORS ,前后端访问跨域问题
java·spring boot·spring