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

相关推荐
AAA修煤气灶刘哥1 小时前
面试官: SpringBoot自动配置的原理是什么?从启动到生效,一文讲透
后端·spring·面试
qq_三哥啊3 小时前
【IDEA】设置Debug调试时调试器不进入特定类(Spring框架、Mybatis框架)
spring·intellij-idea·mybatis
别惹CC4 小时前
Spring AI 进阶之路01:三步将 AI 整合进 Spring Boot
人工智能·spring boot·spring
寒士obj4 小时前
Spring事物
java·spring
IT毕设实战小研12 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
甄超锋13 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
Java小白程序员17 小时前
Spring Framework:Java 开发的基石与 Spring 生态的起点
java·数据库·spring
甄超锋17 小时前
Java Maven更换国内源
java·开发语言·spring boot·spring·spring cloud·tomcat·maven
还是鼠鼠18 小时前
tlias智能学习辅助系统--Maven 高级-私服介绍与资源上传下载
java·spring boot·后端·spring·maven
还是大剑师兰特19 小时前
Spring面试题及详细答案 125道(1-15) -- 核心概念与基础1
spring·大剑师·spring面试题·spring教程