Spring MVC 的核心流程是什么?其实就是一个请求访问某个资源时,会解析 Request Params,最后落到具体的 Controller,再由 Controller 中的逻辑进行相关的业务处理,最后将结果返回给客户端的过程。

我们直到 Spring MVC 核心就是 DispatcherServlet,既然时一个 Servlet,那么就一定由 doGet 和 doPost 方法,我们就从这里开始研究

我们先缕一缕 Spring MVC 加载 handler 的流程

  1. 初始化(查找)所有 @Controller 或 @RequestMapping 的注解(这里会牵扯到父子容器的概念)
  2. 扫描所有 @RequestMappting 的方法
  3. 将 @RequestMappting 的路径和方法绑定在一起,并加入到一个 Map 中去

初始化 @Controller 注解

Spring MVC 通过实现 InitializingBean,重写 afterPropertiesSet() 方法,来完成方法或类的 URI 映射关系

  1. @Override
  2. public void afterPropertiesSet() {
  3. // ★★★ 关键代码:初始化 @Controller 的方法,并生成映射关系
  4. initHandlerMethods();
  5. }

(1)找到符合要求的 bean

获取所有的 bean,并处理符合要求的 bean,符合要求是指类名上含有 @Controller 或 @RequestMapping

  1. protected void initHandlerMethods() {
  2. // 遍历 bean 的名称
  3. for (String beanName : getCandidateBeanNames()) {
  4. if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
  5. // 处理 bean
  6. processCandidateBean(beanName);
  7. }
  8. }
  9. handlerMethodsInitialized(getHandlerMethods());
  10. }
  11. protected void processCandidateBean(String beanName) {
  12. Class<?> beanType = null;
  13. try {
  14. // 获取 bean 的类对象
  15. beanType = obtainApplicationContext().getType(beanName);
  16. }
  17. catch (Throwable ex) {
  18. // An unresolvable bean type, probably from a lazy bean - let's ignore it.
  19. if (logger.isTraceEnabled()) {
  20. logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
  21. }
  22. }
  23. // 判断是否是一个 handler
  24. // 有没有 @Controller 或者 @RequestMapping 注解
  25. if (beanType != null && isHandler(beanType)) {
  26. // 推断 加了 @RequestMapping 的方法,并注册到 mappingRegistry
  27. detectHandlerMethods(beanName);
  28. }
  29. }
  30. @Override
  31. protected boolean isHandler(Class<?> beanType) {
  32. return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
  33. AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
  34. }

(2)推断符合要求的方法

当找到了符合要求的 bean 之后,就开始推断符合要求的方法或类,并将其进行注册

  1. protected void detectHandlerMethods(Object handler) {
  2. Class<?> handlerType = (handler instanceof String ?
  3. obtainApplicationContext().getType((String) handler) : handler.getClass());
  4. if (handlerType != null) {
  5. Class<?> userType = ClassUtils.getUserClass(handlerType);
  6. // 获取所有方法 key = method, value = RequestMappingInfo
  7. Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
  8. (MethodIntrospector.MetadataLookup<T>) method -> {
  9. try {
  10. // ★★★ 返回有 @RequestMapping 的方法,并封装为 RequestMappingInfo
  11. return getMappingForMethod(method, userType);
  12. }
  13. catch (Throwable ex) {
  14. throw new IllegalStateException("Invalid mapping on handler class [" +
  15. userType.getName() + "]: " + method, ex);
  16. }
  17. });
  18. if (logger.isTraceEnabled()) {
  19. logger.trace(formatMappings(userType, methods));
  20. }
  21. methods.forEach((method, mapping) -> {
  22. Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
  23. // 注册到 mappingRegistry 中
  24. // 也就是注册到 urlLookup Map 中去,key = uri, value = RequestMappingInfo
  25. registerHandlerMethod(handler, invocableMethod, mapping);
  26. });
  27. }
  28. }

(3)注册到 urlLookup 中

  1. protected void registerHandlerMethod(Object handler, Method method, T mapping) {
  2. this.mappingRegistry.register(mapping, handler, method);
  3. }
  4. public void register(T mapping, Object handler, Method method) {
  5. this.readWriteLock.writeLock().lock();
  6. try {
  7. HandlerMethod handlerMethod = createHandlerMethod(handler, method);
  8. assertUniqueMethodMapping(handlerMethod, mapping);
  9. this.mappingLookup.put(mapping, handlerMethod);
  10. List<String> directUrls = getDirectUrls(mapping);
  11. for (String url : directUrls) {
  12. // ★★★ 关键代码:加入 urlLookup
  13. this.urlLookup.add(url, mapping);
  14. }
  15. String name = null;
  16. if (getNamingStrategy() != null) {
  17. name = getNamingStrategy().getName(handlerMethod, mapping);
  18. addMappingName(name, handlerMethod);
  19. }
  20. CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
  21. if (corsConfig != null) {
  22. this.corsLookup.put(handlerMethod, corsConfig);
  23. }
  24. this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
  25. }
  26. finally {
  27. this.readWriteLock.writeLock().unlock();
  28. }
  29. }

客户端调用

image.png

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HttpServletRequest processedRequest = request;
  3. HandlerExecutionChain mappedHandler = null;
  4. boolean multipartRequestParsed = false;
  5. // 异步编程(webflux)
  6. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  7. try {
  8. ModelAndView mv = null;
  9. Exception dispatchException = null;
  10. try {
  11. // ★★★ 检查请求是否有上传文件操作
  12. processedRequest = checkMultipart(request);
  13. multipartRequestParsed = (processedRequest != request);
  14. /*
  15. Determine handler for the current request.
  16. ★★★ 推断当前请求(Controller)的处理程序
  17. 关键代码:返回一个 MappedHandler 对象,里面包括要执行的方法以及对应的方法参数
  18. 处理 handler 由两大类型
  19. 1、加上了 @Controller 注解:RequestMappingHandlerMapping
  20. 2、beanName 形式:BeanNameUrlHandlerMapping
  21. */
  22. mappedHandler = getHandler(processedRequest);
  23. if (mappedHandler == null) {
  24. // 返回 404
  25. noHandlerFound(processedRequest, response);
  26. return;
  27. }
  28. /*
  29. Determine handler adapter for the current request.
  30. ★★★ 关键代码:确定当前请求的 handler 的 Adapter(3种方式)
  31. 1、加上了 @Controller 注解:
  32. mappedHandler.getHandler() 返回一个:方法
  33. HandlerAdapter = RequestMappingHandlerAdapter
  34. 2、继承 Controller 接口:
  35. mappedHandler.getHandler() 返回一个:bean
  36. HandlerAdapter = SimpleControllerHandlerAdapter
  37. 3、继承 HttpRequestHandler 接口:
  38. mappedHandler.getHandler() 返回一个:bean
  39. HandlerAdapter = HttpRequestHandlerAdapter
  40. */
  41. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  42. // Process last-modified header, if supported by the handler.
  43. // 获取请求类型
  44. String method = request.getMethod();
  45. // 是否是 GET 请求方式
  46. boolean isGet = "GET".equals(method);
  47. if (isGet || "HEAD".equals(method)) {
  48. // 检查最后修改
  49. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  50. // 从浏览器缓存读取
  51. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  52. return;
  53. }
  54. }
  55. // 前置拦截器处理
  56. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  57. return;
  58. }
  59. // Actually invoke the handler.
  60. // 关键代码:开始执行方法,返回一个 ModelAndView 对象
  61. // 1、如果是 beanName 的方式就比较简单,直接调用接口的实现类的方法即可
  62. // 2、如果是 @Controller 的方式,就是 RequestMappingHandlerAdapter
  63. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  64. if (asyncManager.isConcurrentHandlingStarted()) {
  65. return;
  66. }
  67. // 尝试解析一个默认视图
  68. applyDefaultViewName(processedRequest, mv);
  69. mappedHandler.applyPostHandle(processedRequest, response, mv);
  70. } catch (Exception ex) {
  71. dispatchException = ex;
  72. } catch (Throwable err) {
  73. // As of 4.3, we're processing Errors thrown from handler methods as well,
  74. // making them available for @ExceptionHandler methods and other scenarios.
  75. dispatchException = new NestedServletException("Handler dispatch failed", err);
  76. }
  77. // 关键代码:尝试开始解析视图
  78. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  79. } catch (Exception ex) {
  80. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  81. } catch (Throwable err) {
  82. triggerAfterCompletion(processedRequest, response, mappedHandler,
  83. new NestedServletException("Handler processing failed", err));
  84. } finally {
  85. if (asyncManager.isConcurrentHandlingStarted()) {
  86. // Instead of postHandle and afterCompletion
  87. if (mappedHandler != null) {
  88. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  89. }
  90. } else {
  91. // Clean up any resources used by a multipart request.
  92. if (multipartRequestParsed) {
  93. cleanupMultipart(processedRequest);
  94. }
  95. }
  96. }
  97. }

如何判断是走视图跳转还是JSON

1、@ResponseBody 的形式

先通过 selectHandler 查找一个 handler,如果是 @ResponseBody 注解的话,会找到 RequestResponseBodyMethodProcessor 来处理,判断方式如下:看方法和返回值上是否有该注解

  1. @Override
  2. public boolean supportsReturnType(MethodParameter returnType) {
  3. return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
  4. returnType.hasMethodAnnotation(ResponseBody.class));
  5. }

如果符合条件的话,就会尝试在从 messageConverters 中寻找 JSON 的消息解析器,进行解析,返回到页面

2、String 类型的返回形式

先通过 selectHandler 查找一个 handler,如果返回 String 的话,并且方法返回时 Void,会找到 RequestResponseBodyMethodProcessor 来处理,判断方式如下:

  1. @Override
  2. public boolean supportsReturnType(MethodParameter returnType) {
  3. Class<?> paramType = returnType.getParameterType();
  4. return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
  5. }

如果符合条件的话,将返回值作为视图的名称,设置到 ModelAndViewContainer 中,在判断是否是重定向视图,即 redirect: 开头的字符串,如果是的话:ModelAndViewContainer 设置为重定向

  1. public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
  2. ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
  3. if (returnValue instanceof CharSequence) {
  4. String viewName = returnValue.toString();
  5. // 设置视图名称
  6. mavContainer.setViewName(viewName);
  7. // 是否包含 redirect: 前缀
  8. if (isRedirectViewName(viewName)) {
  9. mavContainer.setRedirectModelScenario(true);
  10. }
  11. }
  12. else if (returnValue != null) {
  13. // should not happen
  14. throw new UnsupportedOperationException("Unexpected return type: " +
  15. returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
  16. }
  17. }
  18. protected boolean isRedirectViewName(String viewName) {
  19. return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName)
  20. || viewName.startsWith("redirect:"));
  21. }

当封装好 ModelAndViewContainer 后(里面已经包含视图名称和是否重定向)开始创建一个 ModelAndView 对象,其实就是 new 了一个 ModelAndView,分别开始处理 Model 和 View

  1. @Nullable
  2. private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
  3. ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
  4. // 处理 Model
  5. modelFactory.updateModel(webRequest, mavContainer);
  6. // 关键代码:如果不需要返回视图,则直接返回 null
  7. if (mavContainer.isRequestHandled()) {
  8. return null;
  9. }
  10. ModelMap model = mavContainer.getModel();
  11. ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
  12. // 检查是不是一个 View 对象,其实就是看看是不是 String
  13. if (!mavContainer.isViewReference()) {
  14. mav.setView((View) mavContainer.getView());
  15. }
  16. // 判断是不是 重定向 的参数
  17. // 即方法中含有 RedirectAttributes 参数,该参数可以传递重定向的参数值
  18. if (model instanceof RedirectAttributes) {
  19. Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
  20. HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
  21. if (request != null) {
  22. RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
  23. }
  24. }
  25. return mav;
  26. }

此时 ModelAndView 对象就已经初始化完成了,下一步开始渲染视图

先通过视图解析器,根据视图名称,查找所需要的视图,然后返回给浏览器,其实核心就是调用转发来实现视图跳转:

  • 如果手动关闭输出流:RequestDispatcher.include(request, response);
  • 如果没有关闭输出流:RequestDispatcher.forward(request, response);

**

  1. @Override
  2. protected void renderMergedOutputModel(
  3. Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  4. // Expose the model object as request attributes.
  5. exposeModelAsRequestAttributes(model, request);
  6. // Expose helpers as request attributes, if any.
  7. exposeHelpers(request);
  8. // Determine the path for the request dispatcher.
  9. String dispatcherPath = prepareForRendering(request, response);
  10. // Obtain a RequestDispatcher for the target resource (typically a JSP).
  11. RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
  12. if (rd == null) {
  13. throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
  14. "]: Check that the corresponding file exists within your web application archive!");
  15. }
  16. // If already included or response already committed, perform include, else forward.
  17. // 判断 输出流 有没有关闭
  18. if (useInclude(request, response)) {
  19. response.setContentType(getContentType());
  20. if (logger.isDebugEnabled()) {
  21. logger.debug("Including [" + getUrl() + "]");
  22. }
  23. // 如果已经手动关闭了输出流 response.getWriter().close()
  24. // 就使用 RequestDispatcher.include(request, response);
  25. rd.include(request, response);
  26. }
  27. else {
  28. // Note: The forwarded resource is supposed to determine the content type itself.
  29. if (logger.isDebugEnabled()) {
  30. logger.debug("Forwarding to [" + getUrl() + "]");
  31. }
  32. // ★★★ 关键代码
  33. // 如果没有关闭输出流 response.getWriter().close()
  34. // 其实就是 RequestDispatcher.forward(request, response);
  35. rd.forward(request, response);
  36. }
  37. }

3、ModelAndView 返回形式

ModelAndView 的形式和 String 的形式类似,只是处理返回值的 handler 是 ModelAndViewMethodReturnValueHandler

  1. @Override
  2. public boolean supportsReturnType(MethodParameter returnType) {
  3. return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
  4. }

接下来,开始处理 ModelAndView

  1. @Override
  2. public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
  3. ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
  4. if (returnValue == null) {
  5. mavContainer.setRequestHandled(true);
  6. return;
  7. }
  8. ModelAndView mav = (ModelAndView) returnValue;
  9. // 判断 view 是不是 String
  10. if (mav.isReference()) {
  11. String viewName = mav.getViewName();
  12. mavContainer.setViewName(viewName);
  13. if (viewName != null && isRedirectViewName(viewName)) {
  14. mavContainer.setRedirectModelScenario(true);
  15. }
  16. }
  17. else {
  18. View view = mav.getView();
  19. mavContainer.setView(view);
  20. if (view instanceof SmartView && ((SmartView) view).isRedirectView()) {
  21. mavContainer.setRedirectModelScenario(true);
  22. }
  23. }
  24. mavContainer.setStatus(mav.getStatus());
  25. // 设置传递需要的参数
  26. mavContainer.addAllAttributes(mav.getModel());
  27. }

此时 ModelAndView 对象就已经初始化完成了,下一步开始渲染视图

先通过视图解析器,根据视图名称,查找所需要的视图,然后返回给浏览器,其实核心就是调用转发来实现视图跳转:

  • 如果手动关闭输出流:RequestDispatcher.include(request, response);
  • 如果没有关闭输出流:RequestDispatcher.forward(request, response);