一、简介

Spring MVC 是 Spring 全家桶中一个重要的组成部分。
Spring MVC 通过策略接口 使得 Spring MVC 框架高度可配置。 Spring MVC 包含多种视图技术,例如 JavaServer Pages(JSP)技术、Velocity、Tiles、iText和POI。Spring MVC 框架并不关心用户使用的是什么视图技术,它只提供相关接口规范。这也使得 Spring MVC 更灵活,定制化开发更简便。

二、项目中的简单使用

直接使用的 spring boot 中 web 模块演示demo

简单的使用代码,如下:

  1. @RequestMapping("/demo")
  2. @RestController //@Controller
  3. public class DemoController {
  4. @Autowired
  5. private JwtTokenUtils jwtTokenUtils;
  6. @RequestMapping("/create-token")
  7. public String generateToken(){
  8. return jwtTokenUtils.createToken(1L);
  9. }
  10. }

Spring MVC 底层依赖的是 spring context 也就是 IOC 容器。通过特定的注解 @Controller(@RestController) ) ,使得被标注的类能够被加载如 IOC 容器中,同时能够被 Spring MVC 识别。然后通过 @RequestMapping(“${url}”) 将方法暴露同时指定资源访问路径 url。

详细的操作可以参考 Spring MVC 官方文档

二、原理 / 流程

02-00.png
先来看看图中各个组件:

  • DispatcherServlet

    DispatcherServlet 是 Spring MVC 的请求入口,被称为 Front Controller 。 接收 request 请求,然后转发各个组件进行处理。

  • HandlerMapping

    DispatcherServlet 第一个转发的组件,根据请求中的请求路径 url,获取对应的 Controller 信息。

  • HandlerAdapter

    根据 HandlerMapping 中返回的 url -> Controller 映射关系,返回 ModelAndView 给 DispatcherServlet。

  • ViewResolver

    ViewResolver 是一个视图解析器,根据 前面得到的 ModelAndView 进行解析。

  • View

    ViewResolver 解析 ModelAndView 之后得到的展示数据。该数据可以是 json ,xml,jsp页面等数据。

我们再来总结一下 Spring MVC 的具体工作流程:

第一步:request 请求被 DispatcherServlet 接收 统一处理

第二步:DispatcherServlet 根据请求从 HandlerMapping 中获取 请求url 对应的映射的 controller,然后将结果返回给 DispatcherServlet

第三步:DispatcherServlet 根据 RequestMapping 返回的 Handler 去 HandlerAdapter 中去获取对应适配的 ModelAndView 返回给 DispatcherServlet

第四步:DispatcherServlet 根据返回的 ModelAndView 去 ViewResolver 中进行解析。最后得到 View

三、源码解析

DispatcherServlet 作为 Spring MVC 的入口,我们就先来看看 DispatcherServlet。如下图,DispatcherServlet 类继承结构:
02-01.png
首先 DispatcherServlet 实现了 ApplicationContextAware,所以DispatcherServlet 能够直接获取 spring 的上下文,也就是 能够直接操作 IOC 容器。

看图我们可以知道 DispatcherServlet 其实就是一个 Servlet 。而说到 Servlet 我们比较关注的就是 doGet() 、doPost() 、doService()方法,还有就是 Servlet 生命周期 init()、destroy()方法。

首先我们来看看 init() 方法的实现

init() 方法没有直接在 DispatcherServlet 中实现,而是由其父类 HttpServletBean 实现,我们简单来看看其中代码:

  1. public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
  2. ..........
  3. // Let subclasses do whatever initialization they like.
  4. initServletBean();
  5. ..........
  6. }

其中比较重要的一个方法就是 initServletBean() ,且这个方法还是其子类 FrameworkServlet 实现。下面我们来看看 FrameworkServlet 部分代码:

  1. public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
  2. ..........
  3. @Override
  4. protected final void initServletBean() throws ServletException {
  5. ..........
  6. this.webApplicationContext = initWebApplicationContext();
  7. ..........
  8. }
  9. protected WebApplicationContext initWebApplicationContext() {
  10. ..........
  11. onRefresh(wac);
  12. ..........
  13. }
  14. protected void onRefresh(ApplicationContext context) {
  15. // For subclasses: do nothing by default.
  16. }
  17. }

看上面可以知道 initServletBean() 中调用的 initWebApplicationContext() ,initWebApplicationContext() 调用的 onRefresh();结果 onRefresh() 由 FrameworkServlet 子类 DispatcherServlet 实现。

从上面我们就可以捋清楚,Servlet 中的 init() 方法,绕了一圈,最后被换了个名字叫做 onRefresh() 在 DispatcherServlet 中被实现了。

有了源头之后,我们再来看看 DispatcherServlet 在初始化 init() 方法所执行的 onRefresh() 究竟做了些什么事

  1. public class DispatcherServlet extends FrameworkServlet {
  2. ..........
  3. @Override
  4. protected void onRefresh(ApplicationContext context) {
  5. initStrategies(context);
  6. }
  7. protected void initStrategies(ApplicationContext context) {
  8. // 初始化 多媒体解析器
  9. initMultipartResolver(context);
  10. // 初始化 位置解析器
  11. initLocaleResolver(context);
  12. // 初始化 主题解析器
  13. initThemeResolver(context);
  14. // 初始化 HandlerMappings
  15. initHandlerMappings(context);
  16. // 初始化 HandlerAdapters
  17. initHandlerAdapters(context);
  18. // 初始化 异常解析器
  19. initHandlerExceptionResolvers(context);
  20. // 初始化 请求到视图名转换器
  21. initRequestToViewNameTranslator(context);
  22. // 初始化 视图解析器
  23. initViewResolvers(context);
  24. // 初始化 FlashMapManager
  25. initFlashMapManager(context);
  26. }
  27. ..........
  28. }

从上面我们可以看到,DispatcherServlet 在初始化的时候,针对 Spring MVC 的工作做了很多的准备工作。

这几个初始化的方法大同小异,我们看看其中的 initHandlerMappings() 初始化 HandlerMapping,

  1. public class DispatcherServlet extends FrameworkServlet {
  2. ..........
  3. HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
  4. ..........
  5. }

可以发现所有的初始化方法,都是直接从IOC 容器中获取。

到这里就完成了 DispatcherServlet 中 init() 的所有初始化方法。

不过为了加深理解 Spring MVC 工作机制,我们再来解析一下 HandlerMapping 究竟是如何被加载进入 IOC 容器中的,并且 HandlerMapping 中都放了些啥。

我们先来看看 HandlerMapping 中类继承结构
02-02.png
在上图的继承结构中,我们又看到了 ApplicationContextAware ,实现了 ApplicationContextAware 就能够持有 Spring 上下文,针对 IOC 容器的操作就变得轻而易举了。
除了 ApplicationContextAware 之外,我们还可以看到 InitializingBean 。InitializingBean接口为实现该接口的 Bean 提供了初始化方法的方式,它只有 afterPropertiesSet() 方法,凡是继承该接口的类,在初始化 Bean 的时候都会执行该方法。

我们以 AbstractHandlerMethodMapping 获取方法url 链接的 HandlerMapping 为例进行源码跟踪。以 afterPropertiesSet() 为入口查看:

  1. public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
  2. ..........
  3. @Override
  4. public void afterPropertiesSet() {
  5. initHandlerMethods();
  6. }
  7. protected void initHandlerMethods() {
  8. // getCandidateBeanNames() 获取 IOC 容器中所有的 beanName
  9. for (String beanName : getCandidateBeanNames()) {
  10. if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
  11. // 遍历并进行处理
  12. processCandidateBean(beanName);
  13. }
  14. }
  15. handlerMethodsInitialized(getHandlerMethods());
  16. }
  17. // 处理方法
  18. protected void processCandidateBean(String beanName) {
  19. Class<?> beanType = null;
  20. try {
  21. // 根据 beanName 从 IOC 容器中获取类
  22. beanType = obtainApplicationContext().getType(beanName);
  23. }
  24. catch (Throwable ex) {
  25. // An unresolvable bean type, probably from a lazy bean - let's ignore it.
  26. if (logger.isTraceEnabled()) {
  27. logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
  28. }
  29. }
  30. // 判定类是否为 handler (判定条件:类拥有 Controller 注解或者 RequestMapping注解)
  31. // 可以跟踪 isHandler() 方法查看
  32. if (beanType != null && isHandler(beanType)) {
  33. // 获取拼接符合要求的 Bean中的 method 的 url
  34. detectHandlerMethods(beanName);
  35. }
  36. }
  37. ..........
  38. }

根据上面的代码 AbstractHandlerMethodMapping 类初始化时,先获取所有容器中 beanName,
然后遍历 beanName,并获取对应实例。判断实例是否有 @Controller @RequestMapping 注解。满足条件的 实例对象进入 detectHandlerMethods() 方法,进行相关方法 url 的拼接。有兴趣的小伙伴可以通过 debug 查看 detectHandlerMethods() 中的操作,能够看到这里面的处理就是将 url 对应的method 以及类之间的关系分别进行存储。

再来看看 doPost、doGet、doService()

这里我们将三个方法一起讲解,因为 doPost() doGet() 最后都调用了 doService() 方法。我们下面来验证一下,
DispatcherServlet 中 doGet() 和 doPost() 在 FrameworkServlet 中实现,下面我们来看看代码:

  1. public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
  2. ..........
  3. @Override
  4. protected final void doGet(HttpServletRequest request, HttpServletResponse response)
  5. throws ServletException, IOException {
  6. processRequest(request, response);
  7. }
  8. @Override
  9. protected final void doPost(HttpServletRequest request, HttpServletResponse response)
  10. throws ServletException, IOException {
  11. processRequest(request, response);
  12. }
  13. rotected final void processRequest(HttpServletRequest request, HttpServletResponse response)
  14. throws ServletException, IOException {
  15. ..........
  16. doService(request, response);
  17. ..........
  18. }
  19. ..........
  20. }

从上面的代码我们可以验证我们的结论,doGet() 、doPost() 最后都是调用的 doService() 方法。

通过查看代码,我们可以知道 doService() 在 FrameworkServlet 中没有实现而是在 DispatcherServlet 中被重写,下面我们来看看 DispatcherServlet 中 doService() 都做了些什么。代码如下:

  1. public class DispatcherServlet extends FrameworkServlet {
  2. ..........
  3. @Override
  4. protected void doService(HttpServletRequest request, HttpServletResponse response)
  5. throws Exception {
  6. ..........
  7. doDispatch(request, response);
  8. ..........
  9. }
  10. ..........
  11. }

在 doService() 中,最主要的调用的就是 doDispatch() 方法。
下面我们来看看 doDispatch() 方法,代码如下:

  1. public class DispatcherServlet extends FrameworkServlet {
  2. ..........
  3. protected void doDispatch(HttpServletRequest request, HttpServletResponse response)
  4. throws Exception {
  5. HttpServletRequest processedRequest = request;
  6. HandlerExecutionChain mappedHandler = null;
  7. boolean multipartRequestParsed = false;
  8. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  9. try {
  10. ModelAndView mv = null;
  11. Exception dispatchException = null;
  12. try {
  13. // 检查本次请求是否是图片上传请求
  14. processedRequest = checkMultipart(request);
  15. multipartRequestParsed = (processedRequest != request);
  16. // Determine handler for the current request.
  17. // 主线第一步、确定当前请求的 handler(也就是 Controller)
  18. // -- 该操作返回 HandlerExecutionChain
  19. // -- HandlerExecutionChain 中存放的是 handler 和 HandlerInterceptor
  20. mappedHandler = getHandler(processedRequest);
  21. if (mappedHandler == null) {
  22. // 如果 handler 不能存在返回 404
  23. noHandlerFound(processedRequest, response);
  24. return;
  25. }
  26. // Determine handler adapter for the current request.
  27. // 主线第二步、确定当前请求对应的handler 的 适配器 handlerAdapter
  28. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  29. // Process last-modified header, if supported by the handler.
  30. // 如果该 handler 支持 ,处理 last-modified 请求头
  31. String method = request.getMethod();
  32. boolean isGet = "GET".equals(method);
  33. if (isGet || "HEAD".equals(method)) {
  34. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  35. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  36. return;
  37. }
  38. }
  39. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  40. return;
  41. }
  42. // Actually invoke the handler.
  43. // 主线第三步、真实调用 handler 中的方法,然后返回对应的视图
  44. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  45. if (asyncManager.isConcurrentHandlingStarted()) {
  46. return;
  47. }
  48. // 主线第四步、根据响应的视图进行视图解析
  49. applyDefaultViewName(processedRequest, mv);
  50. mappedHandler.applyPostHandle(processedRequest, response, mv);
  51. }
  52. catch (Exception ex) {
  53. dispatchException = ex;
  54. }
  55. catch (Throwable err) {
  56. // As of 4.3, we're processing Errors thrown from handler methods as well,
  57. // making them available for @ExceptionHandler methods and other scenarios.
  58. dispatchException = new NestedServletException("Handler dispatch failed", err);
  59. }
  60. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  61. }
  62. catch (Exception ex) {
  63. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  64. }
  65. catch (Throwable err) {
  66. triggerAfterCompletion(processedRequest, response, mappedHandler,
  67. new NestedServletException("Handler processing failed", err));
  68. }
  69. finally {
  70. if (asyncManager.isConcurrentHandlingStarted()) {
  71. // Instead of postHandle and afterCompletion
  72. if (mappedHandler != null) {
  73. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  74. }
  75. }
  76. else {
  77. // Clean up any resources used by a multipart request.
  78. if (multipartRequestParsed) {
  79. cleanupMultipart(processedRequest);
  80. }
  81. }
  82. }
  83. }
  84. ..........
  85. }

doDispatcher() 主线流程如下:
request 通过 doPost、doGet 然后经由 doService() ,最后进入到 doDispatch() 中。
第一步、通过 request 请求获取 对应的 handler (controller)。如果返回对应的handler 为空,则报404 错误。
第二步、根据获取到的 handler 查找对应的 适配器 handlerAdapter
第三步、通反射调用 handler 中对应的方法,然后将返回结果封装成对应的 ModelAndView
第四步、解析 ModelAndView ,得出对应的响应数据 View (可以是 json、xml、jsp等)

在知道了主线流程之后,基本上就能够知道 SpringMVC 究竟做了什么。

但是我还想知道 上面第三步 handle() 里面做了什么,因为在开发过程中我们用到的如 @ReuqestParam 等究竟是怎么完成的。下面我们来跟踪相关的代码,首先我们再来看看相关类结构,
02-03.png
doDispatcher() 主线流程中第三步,handle() 真实调用的是 RequestMappingHandlerAdapter 中的 handleInternal() 方法,现在我们来看看相关代码:

  1. public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
  2. implements BeanFactoryAware, InitializingBean {
  3. ..........
  4. @Override
  5. protected ModelAndView handleInternal(HttpServletRequest request,
  6. HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
  7. ..........
  8. mav = invokeHandlerMethod(request, response, handlerMethod);
  9. ..........
  10. }
  11. @Nullable
  12. protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
  13. HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
  14. ..........
  15. invocableMethod.invokeAndHandle(webRequest, mavContainer);
  16. ..........
  17. }
  18. ..........
  19. }

通过上面的代码,可以得知最终调用的 是 ServletInvocableHandlerMethod 中的 invokeAndHandle() 方法。我们继续追踪代码

  1. public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
  2. ..........
  3. public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
  4. Object... providedArgs) throws Exception {
  5. ..........
  6. Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
  7. ..........
  8. }
  9. ..........
  10. }

此时调用的是 ServletInvocableHandlerMethod 父类 InvocableHandlerMethod 中的方法 invokeForRequest()。继续追踪代码

  1. public class InvocableHandlerMethod extends HandlerMethod {
  2. ..........
  3. @Nullable
  4. public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
  5. Object... providedArgs) throws Exception {
  6. Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
  7. if (logger.isTraceEnabled()) {
  8. logger.trace("Arguments: " + Arrays.toString(args));
  9. }
  10. return doInvoke(args);
  11. }
  12. protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
  13. Object... providedArgs) throws Exception {
  14. if (ObjectUtils.isEmpty(getMethodParameters())) {
  15. return EMPTY_ARGS;
  16. }
  17. MethodParameter[] parameters = getMethodParameters();
  18. Object[] args = new Object[parameters.length];
  19. for (int i = 0; i < parameters.length; i++) {
  20. MethodParameter parameter = parameters[i];
  21. parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
  22. args[i] = findProvidedArgument(parameter, providedArgs);
  23. if (args[i] != null) {
  24. continue;
  25. }
  26. if (!this.resolvers.supportsParameter(parameter)) {
  27. throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
  28. }
  29. try {
  30. args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
  31. }
  32. catch (Exception ex) {
  33. // Leave stack trace for later, exception may actually be resolved and handled..
  34. if (logger.isDebugEnabled()) {
  35. String error = ex.getMessage();
  36. if (error != null && !error.contains(parameter.getExecutable().toGenericString())) {
  37. logger.debug(formatArgumentError(parameter, error));
  38. }
  39. }
  40. throw ex;
  41. }
  42. }
  43. return args;
  44. }
  45. ..........
  46. }

到这里我们就可以看到这里完成了参数与方法中对应参数进行绑定的操作。

以上便是 SpringMVC 整体流程剖析。