1.简介

在前面的文章中,我较为详细的分析了 Spring IOC 和 AOP 部分的源码,并写成了文章。为了让我的 Spring 源码分析系列文章更为丰富一些,所以从本篇文章开始,我将来向大家介绍一下 Spring MVC 的一些原理。在本篇文章中,你将会了解到 Spring MVC 处理请求的过程。同时,你也会了解到 Servlet 相关的知识。以及 Spring MVC 的核心 DispatcherServlet 类的源码分析。在掌握以上内容后,相信大家会对 Spring MVC 的原理有更深的认识。

如果大家对上面介绍的知识点感兴趣的话,那下面不妨和我一起来去探索 Spring MVC 的原理。Let`s Go。

2.一个请求的旅行过程

在探索更深层次的原理之前,我们先来了解一下 Spring MVC 是怎么处理请求的。弄懂了这个流程后,才能更好的理解具体的源码。这里我把 Spring MVC 处理请求的流程图画了出来,一起看一下吧:

如上,每一个重要的步骤上面都有编号。我先来简单分析一下上面的流程,然后再向大家介绍图中出现的一些组件。我们从第一步开始,首先,用户的浏览器发出了一个请求,这个请求经过互联网到达了我们的服务器。Servlet 容器首先接待了这个请求,并将该请求委托给 DispatcherServlet 进行处理。接着 DispatcherServlet 将该请求传给了处理器映射组件 HandlerMapping,并获取到适合该请求的拦截器和处理器。在获取到处理器后,DispatcherServlet 还不能直接调用处理器的逻辑,需要进行对处理器进行适配。处理器适配成功后,DispatcherServlet 通过处理器适配器 HandlerAdapter 调用处理器的逻辑,并获取返回值 ModelAndView。之后,DispatcherServlet 需要根据 ModelAndView 解析视图。解析视图的工作由 ViewResolver 完成,若能解析成功,ViewResolver 会返回相应的视图对象 View。在获取到具体的 View 对象后,最后一步要做的事情就是由 View 渲染视图,并将渲染结果返回给用户。

以上就是 Spring MVC 处理请求的全过程,上面的流程进行了一定的简化,比如拦截器的执行时机就没说。不过这并不影响大家对主过程的理解。下来来简单介绍一下图中出现的一些组件:

组件 说明
DispatcherServlet Spring MVC 的核心组件,是请求的入口,负责协调各个组件工作
HandlerMapping 内部维护了一些 <访问路径, 处理器> 映射,负责为请求找到合适的处理器
HandlerAdapter 处理器的适配器。Spring 中的处理器的实现多变,比如用户处理器可以实现 Controller 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring 不止到怎么调用用户的处理器逻辑。所以这里需要一个处理器适配器,由处理器适配器去调用处理器的逻辑
ViewResolver 视图解析器的用途不难理解,用于将视图名称解析为视图对象 View。
View 视图对象用于将模板渲染成 html 或其他类型的文件。比如 InternalResourceView 可将 jsp 渲染成 html。

从上面的流程中可以看出,Spring MVC 对各个组件的职责划分的比较清晰。DispatcherServlet 负责协调,其他组件则各自做分内之事,互不干扰。经过这样的职责划分,代码会便于维护。同时对于源码阅读者来说,也会很友好。可以降低理解源码的难度,使大家能够快速理清主逻辑。这一点值得我们学习。

3.知其然,更要知其所以然

3.1 追根溯源之 Servlet

本章要向大家介绍一下 Servlet,为什么要介绍 Servlet 呢?原因不难理解,Spring MVC 是基于 Servlet 实现的。所以要分析 Spring MVC,首先应追根溯源,弄懂 Servlet。Servlet 是 J2EE 规范之一,在遵守该规范的前提下,我们可将 Web 应用部署在 Servlet 容器下。这样做的好处是什么呢?我觉得可使开发者聚焦业务逻辑,而不用去关心 HTTP 协议方面的事情。比如,普通的 HTTP 请求就是一段有格式的文本,服务器需要去解析这段文本才能知道用户请求的内容是什么。比如我对个人网站的 80 端口抓包,然后获取到的 HTTP 请求头如下:

如果我们为了写一个 Web 应用,还要去解析 HTTP 协议相关的内容,那会增加很多工作量。有兴趣的朋友可以考虑使用 Java socket 编写实现一个 HTTP 服务器,体验一下解析部分 HTTP 协议的过程。也可以参考我之前写的文章 - 基于 Java NIO 实现简单的 HTTP 服务器

如果我们写的 Web 应用不大,不夸张的说,项目中对 HTTP 提供支持的代码会比业务代码还要多,这岂不是得不偿失。当然,在现实中,有现成的框架可用,并不需要自己造轮子。如果我们基于 Servlet 规范实现 Web 应用的话,HTTP 协议的处理过程就不需要我们参与了。这些工作交给 Servlet 容器就行了,我们只需要关心业务逻辑怎么实现即可。

下面,我们先来看看 Servlet 接口及其实现类结构,然后再进行更进一步的说明。

如上图,我们接下来按照从上到下顺序进行分析。先来看看最顶层的两个接口是怎么定义的。

3.1.1 Servlet 与 ServletConfig

先来看看 Servlet 接口的定义,如下:

  1. public interface Servlet {
  2. public void init(ServletConfig config) throws ServletException;
  3. public ServletConfig getServletConfig();
  4. public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
  5. public String getServletInfo();
  6. public void destroy();
  7. }

init 方法会在容器启动时由容器调用,也可能会在 Servlet 第一次被使用时调用,调用时机取决 load-on-start 的配置。容器调用 init 方法时,会向其传入一个 ServletConfig 参数。ServletConfig 是什么呢?顾名思义,ServletConfig 是一个和 Servlet 配置相关的接口。举个例子说明一下,我们在配置 Spring MVC 的 DispatcherServlet 时,会通过 ServletConfig 将配置文件的位置告知 DispatcherServlet。比如:

  1. <servlet>
  2. <servlet-name>dispatcher</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <init-param>
  5. <param-name>contextConfigLocation</param-name>
  6. <param-value>classpath:application-web.xml</param-value>
  7. </init-param>
  8. </servlet>

如上, 标签内的配置信息最终会被放入 ServletConfig 实现类对象中。DispatcherServlet 通过 ServletConfig 接口中的方法,就能获取到 contextConfigLocation 对应的值。

Servlet 中的 service 方法用于处理请求。当然,一般情况下我们不会直接实现 Servlet 接口,通常是通过继承 HttpServlet 抽象类编写业务逻辑的。Servlet 中接口不多,也不难理解,这里就不多说了。下面我们来看看 ServletConfig 接口定义,如下:

  1. public interface ServletConfig {
  2. public String getServletName();
  3. public ServletContext getServletContext();
  4. public String getInitParameter(String name);
  5. public Enumeration<String> getInitParameterNames();
  6. }

先来看看 getServletName 方法,该方法用于获取 servlet 名称,也就是 标签中配置的内容。getServletContext 方法用于获取 Servlet 上下文。如果说一个 ServletConfig 对应一个 Servlet,那么一个 ServletContext 则是对应所有的 Servlet。ServletContext 代表当前的 Web 应用,可用于记录一些全局变量,当然它的功能不局限于记录变量。我们可通过 标签向 ServletContext 中配置信息,比如在配置 Spring 监听器(ContextLoaderListener)时,就可以通过该标签配置 contextConfigLocation。如下:

  1. <context-param>
  2. <param-name>contextConfigLocation</param-name>
  3. <param-value>classpath:application.xml</param-value>
  4. </context-param>

关于 ServletContext 就先说这么多了,继续介绍 ServletConfig 中的其他方法。getInitParameter 方法用于获取 标签中配置的参数值,getInitParameterNames 则是获取所有配置的名称集合,这两个方法用途都不难理解。

以上是 Servlet 与 ServletConfig 两个接口的说明,比较简单。说完这两个接口,我们继续往下看,接下来是 GenericServlet。

3.1.2 GenericServlet

GenericServlet 实现了 Servlet 和 ServletConfig 两个接口,为这两个接口中的部分方法提供了简单的实现。比如该类实现了 Servlet 接口中的 void init(ServletConfig) 方法,并在方法体内调用了内部提供了一个无参的 init 方法,子类可覆盖该无参 init 方法。除此之外,GenericServlet 还实现了 ServletConfig 接口中的 getInitParameter 方法,用户可直接调用该方法获取到配置信息。而不用先获取 ServletConfig,然后再调用 ServletConfig 的 getInitParameter 方法获取。下面我们来看看 GenericServlet 部分方法的源码:

  1. public abstract class GenericServlet
  2. implements Servlet, ServletConfig, java.io.Serializable {
  3. // 省略部分代码
  4. private transient ServletConfig config;
  5. public GenericServlet() { }
  6. /** 有参 init 方法 */
  7. public void init(ServletConfig config) throws ServletException {
  8. this.config = config;
  9. // 调用内部定义的无参 init 方法
  10. this.init();
  11. }
  12. /** 无参 init 方法,子类可覆盖该方法 */
  13. public void init() throws ServletException { }
  14. /** 未给 service 方法提供具体的实现 */
  15. public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
  16. public void destroy() { }
  17. /** 通过 getInitParameter 可直接从 ServletConfig 实现类中获取配置信息 */
  18. public String getInitParameter(String name) {
  19. ServletConfig sc = getServletConfig();
  20. if (sc == null) {
  21. throw new IllegalStateException(
  22. lStrings.getString("err.servlet_config_not_initialized"));
  23. }
  24. return sc.getInitParameter(name);
  25. }
  26. public ServletConfig getServletConfig() {
  27. return config;
  28. }
  29. // 省略部分代码
  30. }

如上,GenericServlet 代码比较简单,配合着我写注释,很容易看懂。

GenericServlet 是一个协议无关的 servlet,是一个比较原始的实现,通常我们不会直接继承该类。一般情况下,我们都是继承 GenericServlet 的子类 HttpServlet,该类是一个和 HTTP 协议相关的 Servlet。那下面我们来看一下这个类。

3.1.3 HttpServlet

HttpServlet,从名字上就可看出,这个类是和 HTTP 协议相关。该类的关注点在于怎么处理 HTTP 请求,比如其定义了 doGet 方法处理 GET 类型的请求,定义了 doPost 方法处理 POST 类型的请求等。我们若需要基于 Servlet 写 Web 应用,应继承该类,并覆盖指定的方法。doGet 和 doPost 等方法并不是处理的入口方法,所以这些方法需要由其他方法调用才行。其他方法是哪个方法呢?当然是 service 方法了。下面我们看一下这个方法的实现。如下:

  1. @Override
  2. public void service(ServletRequest req, ServletResponse res)
  3. throws ServletException, IOException {
  4. HttpServletRequest request;
  5. HttpServletResponse response;
  6. if (!(req instanceof HttpServletRequest &&
  7. res instanceof HttpServletResponse)) {
  8. throw new ServletException("non-HTTP request or response");
  9. }
  10. request = (HttpServletRequest) req;
  11. response = (HttpServletResponse) res;
  12. // 调用重载方法,该重载方法接受 HttpServletRequest 和 HttpServletResponse 类型的参数
  13. service(request, response);
  14. }
  15. protected void service(HttpServletRequest req, HttpServletResponse resp)
  16. throws ServletException, IOException {
  17. String method = req.getMethod();
  18. // 处理 GET 请求
  19. if (method.equals(METHOD_GET)) {
  20. long lastModified = getLastModified(req);
  21. if (lastModified == -1) {
  22. // 调用 doGet 方法
  23. doGet(req, resp);
  24. } else {
  25. long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
  26. if (ifModifiedSince < lastModified) {
  27. maybeSetLastModified(resp, lastModified);
  28. doGet(req, resp);
  29. } else {
  30. resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
  31. }
  32. }
  33. // 处理 HEAD 请求
  34. } else if (method.equals(METHOD_HEAD)) {
  35. long lastModified = getLastModified(req);
  36. maybeSetLastModified(resp, lastModified);
  37. doHead(req, resp);
  38. // 处理 POST 请求
  39. } else if (method.equals(METHOD_POST)) {
  40. // 调用 doPost 方法
  41. doPost(req, resp);
  42. } else if (method.equals(METHOD_PUT)) {
  43. doPut(req, resp);
  44. } else if (method.equals(METHOD_DELETE)) {
  45. doDelete(req, resp);
  46. } else if (method.equals(METHOD_OPTIONS)) {
  47. doOptions(req,resp);
  48. } else if (method.equals(METHOD_TRACE)) {
  49. doTrace(req,resp);
  50. } else {
  51. String errMsg = lStrings.getString("http.method_not_implemented");
  52. Object[] errArgs = new Object[1];
  53. errArgs[0] = method;
  54. errMsg = MessageFormat.format(errMsg, errArgs);
  55. resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
  56. }
  57. }

如上,第一个 service 方法覆盖父类中的抽象方法,并没什么太多逻辑。所有的逻辑集中在第二个 service 方法中,该方法根据请求类型分发请求。我们可以根据需要覆盖指定的处理方法。

以上所述只是 Servlet 规范中的一部分内容,这些内容是和本文相关的内容。对于 Servlet 规范中的其他内容,大家有兴趣可以自己去探索。好了,关于 Servlet 方面的内容,这里先说这么多。

3.2 DispatcherServlet 族谱

我在前面说到,DispatcherServlet 是 Spring MVC 的核心。所以在分析这个类的源码前,我们有必要了解一下它的族谱,也就是继承关系图。如下:

如上图,红色框是 Servlet 中的接口和类,蓝色框中则是 Spring 中的接口和类。关于 Servlet 内容前面已经说过,下面来简单介绍一下蓝色框中的接口和类,我们从最顶层的接口开始。

● Aware

在 Spring 中,Aware 类型的接口用于向 Spring “索要”一些框架中的信息。比如当某个 bean 实现了 ApplicationContextAware 接口时,Spring 在运行时会将当前的 ApplicationContext 实例通过接口方法 setApplicationContext 传给该 bean。下面举个例子说明,这里我写一个 SystemInfo API,通过该 API 返回一些系统信息。代码如下:

  1. @RestController
  2. @RequestMapping("/systeminfo")
  3. public class SystemInfo implements ApplicationContextAware, EnvironmentAware {
  4. private ApplicationContext applicationContext;
  5. private Environment environment;
  6. @Override
  7. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  8. System.out.println(applicationContext.getClass());
  9. this.applicationContext = applicationContext;
  10. }
  11. @Override
  12. public void setEnvironment(Environment environment) {
  13. this.environment = environment;
  14. }
  15. @RequestMapping("/env")
  16. public String environment() {
  17. StandardServletEnvironment sse = (StandardServletEnvironment) environment;
  18. Map<String, Object> envs = sse.getSystemEnvironment();
  19. StringBuilder sb = new StringBuilder();
  20. sb.append("-------------------------++ System Environment ++-------------------------\n");
  21. List<String> list = new ArrayList<>();
  22. list.addAll(envs.keySet());
  23. for (int i = 0; i < 5 && i < list.size(); i++) {
  24. String key = list.get(i);
  25. Object val = envs.get(key);
  26. sb.append(String.format("%s = %s\n", key, val.toString()));
  27. }
  28. Map<String, Object> props = sse.getSystemProperties();
  29. sb.append("\n-------------------------++ System Properties ++-------------------------\n");
  30. list.clear();
  31. list.addAll(props.keySet());
  32. for (int i = 0; i < 5 && i < list.size(); i++) {
  33. String key = list.get(i);
  34. Object val = props.get(key);
  35. sb.append(String.format("%s = %s\n", key, val.toString()));
  36. }
  37. return sb.toString();
  38. }
  39. @RequestMapping("/beans")
  40. public String listBeans() {
  41. ListableBeanFactory lbf = applicationContext;
  42. String[] beanNames = lbf.getBeanDefinitionNames();
  43. StringBuilder sb = new StringBuilder();
  44. sb.append("-------------------------++ Bean Info ++-------------------------\n");
  45. Arrays.stream(beanNames).forEach(beanName -> {
  46. Object bean = lbf.getBean(beanName);
  47. sb.append(String.format("beanName = %s\n", beanName));
  48. sb.append(String.format("beanClass = %s\n\n", bean.getClass().toString()));
  49. });
  50. return sb.toString();
  51. }
  52. }

如上,SystemInfo 分别实现了 ApplicationContextAware 和 EnvironmentAware 接口,因此它可以在运行时获取到 ApplicationContext 和 Environment 实例。下面我们调一下接口看看结果吧:

如上,我们通过接口拿到了环境变量、配置信息以及容器中所有 bean 的数据。这说明,Spring 在运行时向 SystemInfo 中注入了 ApplicationContext 和 Environment 实例。

● EnvironmentCapable

EnvironmentCapable 仅包含一个方法定义 getEnvironment,通过该方法可以获取到环境变量对象。我们可以将 EnvironmentCapable 和 EnvironmentAware 接口配合使用,比如下面的实例:

  1. public class EnvironmentHolder implements EnvironmentCapable, EnvironmentAware {
  2. private Environment environment;
  3. @Override
  4. public void setEnvironment(Environment environment) {
  5. this.environment = environment;
  6. }
  7. @Override
  8. public Environment getEnvironment() {
  9. return environment;
  10. }
  11. }

● HttpServletBean

HttpServletBean 是 HttpServlet 抽象类的简单拓展。HttpServletBean 覆写了父类中的无参 init 方法,并在该方法中将 ServletConfig 里的配置信息设置到子类对象中,比如 DispatcherServlet。

● FrameworkServlet

FrameworkServlet 是 Spring Web 框架中的一个基础类,该类会在初始化时创建一个容器。同时该类覆写了 doGet、doPost 等方法,并将所有类型的请求委托给 doService 方法去处理。doService 是一个抽象方法,需要子类实现。

● DispatcherServlet

DispatcherServlet 主要的职责相信大家都比较清楚了,即协调各个组件工作。除此之外,DispatcherServlet 还有一个重要的事情要做,即初始化各种组件,比如 HandlerMapping、HandlerAdapter 等。

3.3 DispatcherServlet 源码简析

在第二章中,我们知道了一个 HTTP 请求是怎么样被 DispatcherServlet 处理的。本节,我们从源码的角度对第二章的内容进行补充说明。这里,我们直入主题,直接分析 DispatcherServlet 中的 doDispatch 方法。这里我把请求的处理流程图再贴一遍,大家可以对着流程图阅读源码。

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HttpServletRequest processedRequest = request;
  3. HandlerExecutionChain mappedHandler = null;
  4. boolean multipartRequestParsed = false;
  5. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  6. try {
  7. ModelAndView mv = null;
  8. Exception dispatchException = null;
  9. try {
  10. processedRequest = checkMultipart(request);
  11. multipartRequestParsed = (processedRequest != request);
  12. // 获取可处理当前请求的处理器 Handler,对应流程图中的步骤②
  13. mappedHandler = getHandler(processedRequest);
  14. if (mappedHandler == null || mappedHandler.getHandler() == null) {
  15. noHandlerFound(processedRequest, response);
  16. return;
  17. }
  18. // 获取可执行处理器逻辑的适配器 HandlerAdapter,对应步骤③
  19. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  20. // 处理 last-modified 消息头
  21. String method = request.getMethod();
  22. boolean isGet = "GET".equals(method);
  23. if (isGet || "HEAD".equals(method)) {
  24. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  25. if (logger.isDebugEnabled()) {
  26. logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
  27. }
  28. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  29. return;
  30. }
  31. }
  32. // 执行拦截器 preHandle 方法
  33. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  34. return;
  35. }
  36. // 调用处理器逻辑,对应步骤④
  37. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  38. if (asyncManager.isConcurrentHandlingStarted()) {
  39. return;
  40. }
  41. // 如果 controller 未返回 view 名称,这里生成默认的 view 名称
  42. applyDefaultViewName(processedRequest, mv);
  43. // 执行拦截器 preHandle 方法
  44. mappedHandler.applyPostHandle(processedRequest, response, mv);
  45. }
  46. catch (Exception ex) {
  47. dispatchException = ex;
  48. }
  49. catch (Throwable err) {
  50. dispatchException = new NestedServletException("Handler dispatch failed", err);
  51. }
  52. // 解析并渲染视图
  53. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  54. }
  55. catch (Exception ex) {
  56. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  57. }
  58. catch (Throwable err) {
  59. triggerAfterCompletion(processedRequest, response, mappedHandler,
  60. new NestedServletException("Handler processing failed", err));
  61. }
  62. finally {
  63. if (asyncManager.isConcurrentHandlingStarted()) {
  64. // Instead of postHandle and afterCompletion
  65. if (mappedHandler != null) {
  66. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  67. }
  68. }
  69. else {
  70. if (multipartRequestParsed) {
  71. cleanupMultipart(processedRequest);
  72. }
  73. }
  74. }
  75. }
  76. private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
  77. HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
  78. boolean errorView = false;
  79. if (exception != null) {
  80. if (exception instanceof ModelAndViewDefiningException) {
  81. logger.debug("ModelAndViewDefiningException encountered", exception);
  82. mv = ((ModelAndViewDefiningException) exception).getModelAndView();
  83. }
  84. else {
  85. Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
  86. mv = processHandlerException(request, response, handler, exception);
  87. errorView = (mv != null);
  88. }
  89. }
  90. if (mv != null && !mv.wasCleared()) {
  91. // 渲染视图
  92. render(mv, request, response);
  93. if (errorView) {
  94. WebUtils.clearErrorRequestAttributes(request);
  95. }
  96. }
  97. else {
  98. if (logger.isDebugEnabled()) {...
  99. }
  100. if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
  101. return;
  102. }
  103. if (mappedHandler != null) {
  104. mappedHandler.triggerAfterCompletion(request, response, null);
  105. }
  106. }
  107. protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
  108. Locale locale = this.localeResolver.resolveLocale(request);
  109. response.setLocale(locale);
  110. View view;
  111. /*
  112. * 若 mv 中的 view 是 String 类型,即处理器返回的是模板名称,
  113. * 这里将其解析为具体的 View 对象
  114. */
  115. if (mv.isReference()) {
  116. // 解析视图,对应步骤⑤
  117. view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
  118. if (view == null) {
  119. throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
  120. "' in servlet with name '" + getServletName() + "'");
  121. }
  122. }
  123. else {
  124. view = mv.getView();
  125. if (view == null) {
  126. throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
  127. "View object in servlet with name '" + getServletName() + "'");
  128. }
  129. }
  130. if (logger.isDebugEnabled()) {...}
  131. try {
  132. if (mv.getStatus() != null) {
  133. response.setStatus(mv.getStatus().value());
  134. }
  135. // 渲染视图,并将结果返回给用户。对应步骤⑥和⑦
  136. view.render(mv.getModelInternal(), request, response);
  137. }
  138. catch (Exception ex) {
  139. if (logger.isDebugEnabled()) {...}
  140. throw ex;
  141. }
  142. }

以上就是 doDispatch 方法的分析过程,我已经做了较为详细的注释,这里就不多说了。需要说明的是,以上只是进行了简单分析,并没有深入分析每个方法调用。大家若有兴趣,可以自己去分析一下 doDispatch 所调用的一些方法,比如 getHandler 和 getHandlerAdapter,这两个方法比较简单。从我最近所分析的源码来看,我个人觉得处理器适配器 RequestMappingHandlerAdapter 应该是 Spring MVC 中最为复杂的一个类。该类用于对 @RequestMapping 注解的方法进行适配。该类的逻辑我暂时没看懂,就不多说了,十分尴尬。关于该类比较详细的分析,大家可以参考《看透Spring MVC》一书。

4.总结

到此,本篇文章的主体内容就说完了。本篇文章从一个请求的旅行过程进行分析,并在分析的过程中补充了 Servlet 和 DispatcherServlet 方面的知识。在最后,从源码的角度分析了 DispatcherServlet 处理请求的过程。总的来算,算是做到了循序渐进。当然,限于个人能力,以上内容可能会有一些讲的不好的地方,这里请大家见谅。同时,也希望大家多多指教。

好了,本篇文章先到这里。谢谢大家的阅读。

参考

附录:Spring 源码分析文章列表

Ⅰ. IOC

更新时间 标题
2018-05-30 Spring IOC 容器源码分析系列文章导读
2018-06-01 Spring IOC 容器源码分析 - 获取单例 bean
2018-06-04 Spring IOC 容器源码分析 - 创建单例 bean 的过程
2018-06-06 Spring IOC 容器源码分析 - 创建原始 bean 对象
2018-06-08 Spring IOC 容器源码分析 - 循环依赖的解决办法
2018-06-11 Spring IOC 容器源码分析 - 填充属性到 bean 原始对象
2018-06-11 Spring IOC 容器源码分析 - 余下的初始化工作

Ⅱ. AOP

更新时间 标题
2018-06-17 Spring AOP 源码分析系列文章导读
2018-06-20 Spring AOP 源码分析 - 筛选合适的通知器
2018-06-20 Spring AOP 源码分析 - 创建代理对象
2018-06-22 Spring AOP 源码分析 - 拦截器链的执行过程

Ⅲ. MVC

更新时间 标题
2018-06-29 Spring MVC 原理探秘 - 一个请求的旅行过程
2018-06-30 Spring MVC 原理探秘 - 容器的创建过程

本文在知识共享许可协议 4.0 下发布,转载需在明显位置处注明出处

作者:田小波

本文同步发布在我的个人博客:http://www.tianxiaobo.com


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

Spring MVC 原理探秘 - 一个请求的旅行过程的更多相关文章

  1. Spring MVC 原理探秘 - 容器的创建过程

    1.简介 在上一篇文章中,我向大家介绍了 Spring MVC 是如何处理 HTTP 请求的.Spring MVC 可对外提供服务时,说明其已经处于了就绪状态.再次之前,Spring MVC 需要进行 ...

  2. 精尽Spring MVC源码分析 - 一个请求的旅行过程

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  3. spring MVC原理

    spring MVC原理   Spring MVC工作流程图   图一   图二    Spring工作流程描述       1. 用户向服务器发送请求,请求被Spring 前端控制Servelt D ...

  4. Spring MVC体系结构和处理请求控制器

    Spring MVC体系结构和处理请求控制器 一:MVC设计模式: (1.)数据访问接口:DAO层 (2.)处理业务逻辑层:Service层 (3.)数据实体:POJO (4.)负责前段请求接受并处理 ...

  5. Spring学习 6- Spring MVC (Spring MVC原理及配置详解)

    百度的面试官问:Web容器,Servlet容器,SpringMVC容器的区别: 我还写了个文章,说明web容器与servlet容器的联系,参考:servlet单实例多线程模式 这个文章有web容器与s ...

  6. Spring MVC原理及实例基础扫盲篇

    近期 项目中刚接触了SpringMVC,就把这几天看的跟实践的东西写出来吧. 一.首先,先来了解一下SpringMVC究竟是个什么样的框架? Spring Web MVC是一种基于Java的实现了We ...

  7. Spring MVC原理及配置

    Spring MVC原理及配置 1. Spring MVC概述 Spring MVC是Spring提供的一个强大而灵活的web框架.借助于注解,Spring MVC提供了几乎是POJO的开发模式,使得 ...

  8. Spring MVC的handlermapping之请求分发如何找到正确的Handler(BeanNameUrlHandlerMapping,SimpleUrlHandlerMapping)

    本文讲的是Spring MVC如何找到正确的handler, 前面请求具体怎么进入到下面的方法,不再细说. 大概就是Spring mvc通过servlet拦截请求,实现doService方法,然后进入 ...

  9. Ajax+Spring MVC实现跨域请求(JSONP)JSONP 跨域

    JSONP原理及实现 接下来,来实际模拟一个跨域请求的解决方案.后端为Spring MVC架构的,前端则通过Ajax进行跨域访问. 1.首先客户端需要注册一个callback(服务端通过该callba ...

随机推荐

  1. c# post 接收传来的文件

    private void UploadFile() { // //......其他代码 // HttpFileCollection files = HttpContext.Current.Reques ...

  2. mysql timestamp字段定义的

    Cause: java.sql.SQLException: Cannot convert value '2017-07-26 20:40:41.000000' from column 10 to TI ...

  3. TP5 自定义验证器

    TP内置验证功能提供两种验证方法 验证器(推荐) $validate = Validate::make([ 'id' => 'require|integer', ]); if ($validat ...

  4. Python模块定义和使用

    Python中所谓的模块就是一个Python文件,一个abc.py的文件就是一个名字叫abc的模块,一个xyz.py的文件就是一个名字叫xyz的模块.模块由代码.函数或类组成.编程中使用模块不仅可以提 ...

  5. LevelDB源码分析-TableBuilder生成sstable

    TableBuilder生成sstable(include/table_builder.h table/table_builder.cc) LevelDB使用TableBuilder来构建sstabl ...

  6. 4、订单详情 /items/order/detail?orderNo=201903251750380001

    <template> <div class="write"> <div class="adr"> <div class ...

  7. java学习笔记(八):继承、extends、super、this、final关键字

    继承解决代码重用的问题,方便管理和维护代码. 继承 子类拥有父类非private的属性,方法. 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展. 子类可以用自己的方式实现父类的方法. Java ...

  8. SpringMVC 中模型数据处理中的@ModelAttribute 和@SessionAttributes使用细节

    @ModelAttribute 运用在参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中,便于View层使用(在方法中修改了入参值在view层得到的还是 ...

  9. gdbserver

    这里写下gdbserver的用法: 两台机子,宿主机A和目标机B. step1: 我们在B上安装gdbserver,在A上编译可执行程序a.out,把a.out拷贝到B上面去. step2: 在A上打 ...

  10. fabric 持久化

    每个容器都有目录需要映射出来.在volume中添加如下映射即可: peer是: /var/hyperledger/peer{number}/org{number}:/var/hyperledger/p ...