【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 处理请求的一个过程,下节我们会细看请求参数的解析和绑定,给加解密去找到一个合适的处理方式,有理解不对的地方欢迎指点。

浙公网安备 33010602011771号