【Spring MVC】MVC请求的处理过程一览

1  前言

最近工作上遇到一个加解密的需求,服务是我们传统的 SpringBoot 搭建的,Spring MVC 是我们服务的控制层,用于处理、响应请求,那么接下来带着回忆的角度看一下请求的处理过程、以及请求参数的解析和响应参数写入处理都是怎么样的,然后我们看看加解密处理的时机。

本节我们主要看下请求的处理过程,MVC 我们知道的一个核心就是 DispatcherServlet,我们就看看 DispatcherServler 是怎么到我们的 Controller 的,看这一条链路。

大家如果还想知道:

DispatcherServlet 是如何注入到 Tomcat 中的,可以看我的这篇:【Spring MVC + Tomcat】Spring MVC 传统VS现代方式的启动过程对比 

Tomcat 处理请求的过程,可以看我的这篇:【Tomcat】Tomat 处理请求的过程(图解)

源码环境:【问题记录】【Spring】Spring-framework 源码环境搭建

可以帮助大家更好的理解请求的处理过程。

2  环境准备

我们先准备一下环境,边回忆边调试增强理解,一般我们接收请求参数大概有这四个注解:

@PathVariable:路径参数
@RequestParam:简单参数 ?后边的简单参数
@ModelAttribute:复杂对象参数 ?后边的复杂参数
@RequestBody:请求体参数

实体信息:

Controller 信息:

请求结果调试:

假设我们最后就是给 orderNo 订单号进行加解密的需求哈,我们本节先看看请求的处理大致过程,下节看每种参数的解析细节处理,最后看看如何对订单号进行加解密处理。

3  MVC 请求处理一览

3.1  FrameworkServlet-processRequest

我们从哪里开始看起呢,就从下边这张图看起:

简单解释一下:

java里的 servlet 接口中定义了一个 service 方法表示处理请求,两个参数就是我们熟悉的:servletRequest 和 servletResponse

然后 HttpServlet 类实现了 servlet 接口,实现了 service 方法,主要实现逻辑是将 servletRequest 转为 HttpServletRequest 、servletResponse 转为 HttpServletResponse,并根据不同的请求方法调用到相应的 doXXX方法

mvc里的 FrameworkServlet 又继承了 httpServlet 将各种 doXXX方法又汇总到了 processRequest 方法,那么我们这里就从该方法开始看起:

// FrameworkServlet#processRequest
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
    // 两个上下文
    // 语言上下文
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    // 这里 DispatcherServlet 会复写 buildLocaleContext 方法
    // FrameworkServlet 默认返回的是 SimpleLocaleContext 只有语言信息
    // DispatcherServlet 返回的是 SimpleTimeZoneAwareLocaleContext 在语言的基础上增加了一个时区的信息
    LocaleContext localeContext = buildLocaleContext(request);

    // 请求的上下文 默认是 ServletRequestAttributes
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    // 初始化上下文 也就是把上边的两个对象 放到当前的线程上下文里
    initContextHolders(request, localeContext, requestAttributes);

    try {
        // 处理请求
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        // 还原上下文
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        // 打印日志
        logResult(request, response, failureCause, asyncManager);
        // 发布请求已处理事件 ServletRequestHandledEvent
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

在 processRequest 方法中,我们主要看到有两个上下文,LocaleContextHolder 可以获取到语言以及时区信息,RequestContextHolder 可以获取到当前线程的请求上下文信息,比如我们可以放一些用户信息、权限信息等。

然后实际处理请求是 doService 的抽象方法:

protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
            throws Exception;

接下来我们就看看 DispatcherServlet 的 doService 方法。

3.2  DispatcherServlet-doService

我们直接看,主要是打印一下请求信息,然后写入一些框架的属性信息,最后是走到 doDispatch 方法:

// DispatcherServlet#doService
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 打印请求信息
    logRequest(request);
    // ...
    // 写入一些 mvc 的属性
    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
    // ...
    try {
        // dispatch 方法
        doDispatch(request, response);
    }
    finally {
        // ...
    }
}

那我们跟进去看 doDispatch 方法。

3.3  DispatcherServlet-doDispatch

我们直接看:

// DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    // 处理请求 handler 我们的 controller 里的每个方法一般都会解析为某个 mappedHandler 但注意这里是执行链对象
    // 会有拦截器的相关处理
    HandlerExecutionChain mappedHandler = null;
    // 是否是文件上传的请求
    boolean multipartRequestParsed = false;
    // 异步请求的处理 暂且不看
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        ModelAndView mv = null;
        Exception dispatchException = null;
        try {
            // 检查是否是上传文件的请求 主要是通过请求的请求头里的 content-type 的值中是否有 multipart
            // 这个里边会有文件大小的限制 比如我们常见的 MaxUploadSizeExceededException
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);
            // 获得当前请求的一个处理链
            mappedHandler = getHandler(processedRequest);
            // 如果为空的话 就是我们常见看到的 404 not found异常
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }
            // 处理器 handler 要找到一个能执行他的 adapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            // 处理缓存相关的 header
            // Process last-modified header, if supported by the handler.
            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;
                }
            }
            // 执行处理器的前置处理 如果有一个 拦截器返回false 就直接返回
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }
            // 执行实际的业务逻辑
            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            applyDefaultViewName(processedRequest, mv);
            // 执行后置处理
            // 可以看到前置和后置的最大区别 前置是有返回的  后置是没有返回的
            // 前置有一个不满足就会直接结束请求  后置不需要判断 只要不抛异常即可(也可以理解 因为实际的业务都处理完了,后置一般就是做清理的)
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        // 解析结果以及执行拦截器的  afterCompletion 方法
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    // 当 dispatch 出现异常的时候 才会执行拦截器的 afterCompletion 方法
    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);
            }
        }
    }
}

主要做的事情是:

(1)判断是否是文件上传的请求,是的话进行解析,这里文件比较大的话,会出现我们常看到的大小限制异常,这里再多说一点,比如 Tomcat 默认限制的是 1M,那我要上传 10M的文件,是等我10M都上传完,再给我返回异常么,其实不是当你文件上传的时候,TCP里有个PUSH标志会优先推送给 MVC处理,然后发现你请求头的文件大小超长了,就直接返回异常并且 RST 掉你的请求。

(2)找到一个处理该请求的执行链 HandlerExecutionChain

(3)找到执行该执行链的适配器 adapter

(4)执行拦截器的前置处理 preHandle (这里注意当某个前置处理返回false 的话,就会执行该拦截器的 afterCompletion 并结束此次请求

(5)适配器执行

(6)执行拦截器的后置处理 postHandle

(7)解析结果视图

(8)当 dispatch 过程中发生异常会执行拦截器的 afterCompletion 方法

(9)打扫战场

接下来我们主要看下 (2)、(3)、(5)。

3.4  DispatcherServlet-getHandler 执行链的获取

我们直接看:

/**
 * DispatchServlet#getHandler
 * 获取处理器执行链
 * @param request 当前请求
 * @return
 * @throws Exception
 */
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 内部的映射集合
    // private List<HandlerMapping> handlerMappings;
    if (this.handlerMappings != null) {
        // 逐个遍历
        for (HandlerMapping mapping : this.handlerMappings) {
            // 获取处理器执行链 可以看到根据集合中的某个映射关系得到一个执行链
            HandlerExecutionChain handler = mapping.getHandler(request);
            // 得到一个就可以返回了
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

可以看到就是遍历 DispatchServlet 内部的 handlerMappings 集合来得到一个执行链。那么这个集合的由来就变的尤为重要。大概有两方面的思路:

(1)DispatchServlet 注入到 Tomcat 里的 Servlet 容器

(2)Tomcat 里的请求处理,如果Servlet 还未初始化会调用初始化方法,在这里就会初始化 handlerMappings 集合

关于 DispatchServlet 注入到 Tomcat 的,我这里画了个图可以看看:

然后当请求进入到 Tomcat 的时候,大家可以看我上边的 Tomcat 的请求处理过程,如果 Servlet 未初始化的话,会调用 Servlet 的初始化,大家可以打个断点调试下,我这里就直接从初始化开始看起:

对于 HandlerMapping 有一个重要的 RequestMappingHandlerMapping实现,它里边包含了我们平时写的99%的controller,那我们看下这个的由来。

3.4.1  RequestMappingHandlerMapping 的由来

我这里也是直接画图了哈:

看了他的由来以后,那么它内部的路径映射信息是什么时候填充的呢?我们的那些 controller 的信息是什么时候解析的呢?我们看一下他的类图,会看到他实现了 InitializingBean 接口:

所以他的填充大概就是在 afterPropertiesSet 方法中执行的,我们看下:

这是路由初始化完毕,那我们紧接着结合看看 getHandler 方法,看看是如何根据某个请求去匹配得到一个 handler 的:

看下来大家要知道这几点:

(1)我们平时写的 Controller 里的每个方法都会封装一个 HandlerMethod 一个 MappingRegistration 都是存放在了 AbstractHandlerMethodMapping 类的内部类 MappingRegistry 里。

(2)根据当前请求匹配得到的 HandlerMethod 然后再根据所有的拦截器逐个匹配当前请求得到的拦截器组成一个 HandlerChain。

3.5  DispatcherServlet-getHandlerAdapter 适配器的获取

我们继续看下怎么给 handler 寻找执行他的适配器:

// DispatcherServlet#getHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            // 当前适配器是否支持 支持的话直接返回
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
// 默认的四个适配器支持范围:
// RequestMappingHandlerAdapter 支持 handler instanceof HandlerMethod
// HandlerFunctionAdapter 支持 handler instanceof HandlerFunction
// HttpRequestHandlerAdapter 支持 handler instanceof HttpRequestHandler
// SimpleControllerHandlerAdapter 支持 handler instanceof Controller

主要就是遍历每个适配器,只要找到一个就直接返回,这里我们只要知道我们的 Controller 基本都是这个适配器 RequestMappingHandlerAdapter。

3.5  DispatcherServlet-handle 执行

有了执行器,有了适配器,那我们再看看执行过程:

ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

我们主要看下 invokeHandlerMethod 方法:

// RequestMappingHandlerAdapter#invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    // 创建封装了 request 和 response 的 ServletWebRequest 对象
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // 获取 WebDataBinderFactory 实例,用于参数绑定与验证
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);

        // 获取 ModelFactory 实例,用于管理控制器方法执行前的模型状态
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        // 创建可调用的处理器方法对象 ServletInvocableHandlerMethod
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);

        // 设置参数解析器(如果存在)
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }

        // 设置返回值处理器(如果存在)
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }

        // 设置数据绑定工厂和参数名发现器
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

        // 创建 ModelAndViewContainer 用于存储模型数据和视图信息
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();

        // 添加从 FlashMap 中恢复的属性到模型容器中
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));

        // 初始化模型数据
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);

        // 设置是否忽略默认模型中的重定向属性
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        // 创建异步请求对象 AsyncWebRequest 并设置超时时间
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        // ...异步略过

        // 调用处理器方法并处理返回值 这里就要开始解析参数执行我们的 controller了
        invocableMethod.invokeAndHandle(webRequest, mavContainer);

        // 构造并返回 ModelAndView 对象
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        // 请求完成后清理资源
        webRequest.requestCompleted();
    }
}

最后再看一下 getModelAndView 方法:

// RequestMappingHandlerAdapter#getModelAndView
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

    // 更新模型,根据请求和容器中的信息
    modelFactory.updateModel(webRequest, mavContainer);
    // 如果请求已经被处理,直接返回null,不再创建ModelAndView
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    // 获取更新后的模型
    ModelMap model = mavContainer.getModel();
    // 创建ModelAndView对象,使用容器中的视图名称、模型和状态
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    // 如果容器中的视图不是引用视图名称,则设置实际的视图对象
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    // 如果模型是一个重定向属性实例,则处理闪存属性
    if (model instanceof RedirectAttributes) {
        Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        // 如果请求不为空,将闪存属性添加到输出闪存映射中
        if (request != null) {
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
    }
    // 返回创建的ModelAndView对象
    return mav;
}

最后适配器的执行,我们下篇会细看,因为会涉及到各种参数的绑定注入,以及响应的数据处理等。

4  小结

好啦,本节我们主要回顾下 MVC 处理请求的一个过程,下节我们会细看请求参数的解析和绑定,给加解密去找到一个合适的处理方式,有理解不对的地方欢迎指点。

posted @ 2025-06-02 18:52  酷酷-  阅读(73)  评论(0)    收藏  举报