SpringMVC流程源码解析及SpringMVC相关知识点(Spring父子容器,SpringBoot对映射处理器功能拓展实现对静态资源的访问原理)
本文主要对SpringMVC从接收一个请求,到请求处理结束返回给浏览器的整个过程源码进行学习。为了更好明确学习目标,我们先将上篇文章中SpringMVC的工作流程图还是展示一下,针对图中每一个流程在源码上进行解释其设计和实现思路,中间可能会穿插其它相关联的SpringMVC知识点,这部分关联知识点会分别用独立的文章进行链接描述。最后再对整个工作流程的源码实现进行总结。本文中的代码样例来源于上一篇应用文章https://blog.csdn.net/qq_20395245/article/details/106304402,为了便于理解代码下面只将Controller.java的内容发出来。
上图就是SpringMVC的工作流程图:
1.首先用户发送http请求到前端控制器DispatchServlet上,
2.前端控制器调用映射处理器HandlerMappings,HandlerMapping根据配置或注解找到具体处理器handler,
3.HandlerMapping将具体handler返回给DispatchServlet,
4.前端控制器拿着handler去请求HandlerAdatpers,找到一个与handler匹配的适配器Adapter,
5.通过适配器传入请求和具体的处理器handler(Controller或Action),执行handler方法处理请求,
6.handler被执行完毕后返回ModelAndView给处理器适配器HandlerAdatper,
7.HandlerAdatper将得到的ModelAndView返回给前端控制器,
8.前端控制器将ModelAndView传给视图解析器ViewResolver,
9.视图解析器对ViewResource进行解析后返回具体的VIew给前端控制器,
10.前端控制器根据View进行渲染视图(将模型数据填充到视图中),
11.把渲染好的视图把过response响应给用户。
下图是MyController.java控制器的内容:
[code]package com.myspringmvc.controller; import org.apache.http.HttpResponse; import org.springframework.http.HttpRequest; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import java.util.HashMap; import java.util.Map; @Controller @RequestMapping(value="/web") public class MyController { @RequestMapping(value="/index", method = RequestMethod.GET) public String indexPage(){ return "index"; } @RequestMapping(value="/hi", method = RequestMethod.GET) @ResponseBody public String sayHi(String name){ System.out.println("sayHi调用了..."); return "hello "+name; } }
我们知道,当我们在浏览器发起http请求http://localhost:9090/web/hi?name=yang时,浏览器最终会响应
那么我们的这个http请求会最先到达SpringMVC的哪里呢?根据上面的工作流程可知,这个请求会先到达一个名为DispatcherServlet的前端控制器上。那么让我们看一下这个DispatcherServlet是什么呢,这个前端控制器接收到这个http请求后接下来会做什么事情呢?点开这个DispatcherServlet我们可以发现它实际上就是一个HttpServlet:
也就是说实际上DispatcherServlet接收请求就相当于是一个特殊的Servlet接收了这个请求。我们知道servlet接收请求后会调用一些servlet方法例如doGet,doPost,doService等。这是一个Get类型的http请求,因此这里会执行doGet的方法,实际上这个方法会在FrameworkServlet中调用,实际处理方法是:
这个processRequest方法内doService(request,response)之前主要对请求的request做一些数据转化和封装,核心逻辑在doService方法中,而doService内部的核心代码在925行的doDispatch上,
继续点开,这个方法内就做了比较多的事情了,需要单独拎出来进行必要的注释。
[code] /** * Process the actual dispatching to the handler. * <p>The handler will be obtained by applying the servlet's HandlerMappings in order. * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters * to find the first that supports the handler class. * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers * themselves to decide which methods are acceptable. * @param request current HTTP request * @param response current HTTP response * @throws Exception in case of any kind of processing failure */ protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; //创建要返回的ModelAndView对象 Exception dispatchException = null; try { //checkMultipart这一步是判断这个请求是不是一个带文件的请求,比如文件上传, //这种请求会将http请求头的content-type是不是以multipart/前缀 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); //根据当前请求查找匹配的handlerMapping,并且查找到请求对应的handler mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. // 找到找能处理这个请求的的handler后,找到与之匹配的适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } 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); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } 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); } } } }
[code]doDispatch方法分析:
首先,这个方法会先判断这个请求是不是一个带文件请求比如文件上传processedRequest = checkMultipart(request);
对于这种请求有独立的处理逻辑(960行内)。
本例中显然不是,那么接下来就会拿这个请求去根据当前请求查找匹配的handlerMapping并且会在handlerMapping中找到能处理这个请求的handler(964行),这行代码说明包含了两个步骤,分别是流程图中的步骤2和步骤3。
那么这个查找handlerMapping的过程以及匹配处理请求handler的实现是怎么样的呢?让我们进入到这个方法中一看究竟:
[code]mappedHandler = getHandler(processedRequest);
这个方法就比较值得研究了。我们可以看到,要想为请求匹配到正确的handlerMapping,实际上是在handlerMappings这个Map集合中进行遍历查找。通过调试我们注意到,这个handlerMappings可以看到有5个,如下所示。其中WebMvcConfigurationSupportXXX是内部其它用途的handler映射器,我们主要关注RequestMappingHandlerMapping和BeanNameUrlHandlerMapping。
这个handlerMappings就是流程图中第2步所指的映射处理器集合,也就是说映射处理器集合实际上一个List,它存储多种类型的映射处理器对象。当然handlerMappings为什么有这些映射处理器,这些映射处理器是什么以及何时被加到handlerMappings中的?handlerMappings是很重要的一个组件,例如SpringBoot发布的应用可以通过请求链接对resources等几个指定目录下静态文件进行访问,这个是SpringMVC不支持的功能但是SpringBoot对这个知识点进行了拓展。限于篇幅对handlerMappings的相关知识点请参考小篇的文章
https://www.geek-share.com/detail/2805546980.html
在上面程序中拿到映射处理器集合HandlerMappings后,根据流程图我们知道下一步就是根据配置或注解在每一个映射处理器中找具体处理器handler。这个核心逻辑在1188行,这一行代码的作用是根据request请求地址,在匹配的handlerMapping映射器内搜索到匹配的handler,当然这个handler实际是一个调用链handler,其内部处理处理handler之外还绑定了多个拦截Interceptor,拦截器相当于在请求真正处理前后可以进行一些逻辑操作,有点像AOP功能,关于拦截器的知识这里不展开:
[code]HandlerExecutionChain handler = hm.getHandler(request);
让我们点开这个hm.getHandler(request)看看内部是怎么实现的?
这个方法的核心查找逻辑在350行的
[code]Object handler = getHandlerInternal(request);
继续点开看,在这个核心方法里进行一些必要的注释说明,由于上面是@RequestMapping方式注册的映射因此是被
RequestMappingHandlerMapping这个映射处理器处理的(这个知识点也会在下面和handlerMappings一起说明
),
点开RequestMappingHandlerMapping.getHandlerInternal方法如下:
[code] /** * Look up a handler method for the given request. */ @Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //根据当前请求获取到请求路径,本例是由http://localhost:9090/web/hi?name=yang请求 //因此这里解析出来的请求路径lookupPath=/web/hi String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); if (logger.isDebugEnabled()) { logger.debug("Looking up handler method for path " + lookupPath); } this.mappingRegistry.acquireReadLock(); try { //根据解析出的请求路径lookupPath=/web/hi,去查找能处理这个请求的handler方法并返回 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); if (logger.isDebugEnabled()) { if (handlerMethod != null) { logger.debug("Returning handler method [" + handlerMethod + "]"); } else { logger.debug("Did not find handler method for [" + lookupPath + "]"); } } return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } }
上面最主要的两行核心查找代码是
//根据当前请求获取到请求路径,本例是由http://localhost:9090/web/hi?name=yang请求
//因此这里解析出来的请求路径lookupPath=/web/hi
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//根据解析出的请求路径lookupPath=/web/hi,去查找能处理这个请求的handler方法并返回
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
从上面我们就看到了对于使用了@RequestMapping方式注册,通过请求名通过在映射处理器中查找最终就得到了一个handlerMethod,这个handlerMethod就是能处理这个请求(本例的/web/hi)的全限定方法名(红色框出的部分),然后这个handler就会返回给DispatchServlet。
很显然核心搜索逻辑在HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
我们继续看其底层是怎么进行搜索的:
这个方法内的核心代码已经如上红色框出。对
[code]List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
这行代码的作用是根据请求路径(如/web/hi)去一个名为urlLookup的Map集合中找对应的RequestMappingInfo对象集合,每个RequestMappingInfo对象实际上就代表一个请求映射对象,这里为什么是返回一个List<T>集合呢?因为我们可以在程序中使用多个方法来接收同一个请求,例如可以分别给方法A和方法B映射到/web/hi方法上,因此这里返回的就是两个请求映射对象。另外这个urlLookup的Map内部的RequestMappingInfo数据对象什么时候put到集合中的下面会解释。
拿到请求地址对应的请求映射对象集合后,对这个映射对象集合directPathMatches 进行遍历操作,对每一个RequestMappingInfo映射对象进行handler的查找,
[code]if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); }
点开这个方法:
根据每个请求映射对象查找handler的逻辑就在 this.mappingRegistry.getMappings().get(mapping)
实际上,根据请求路径在映射处理器中查找具体处理器handler的最底层逻辑,就是根据请求地址对应的请求映射对象,在一个名为mappingLookup的Map集合中查找能处理这个请求的handler,而这个handler是一个全限定的方法名。
综上,对于http--->DispatchServlet-->调用HandlerMapping查找--->handler 这两步流程,最底层逻辑就是:
根据请求路径,在一个名为mappingLookup的Map集合中查找能处理这个请求的handler,
而mappingLookup的数据结构为Map<请求地址URL,全限定方法名>:
关于一个Controller类里面请求路径和方法是怎么以K,V关系绑定到HandlerMapping中的实现原理可以概括如下:
[code]* 1.扫描整个项目找到所有的类 * 2.判断类是否有Controller或RequestMapping修饰 * 3.扫描这些有修饰的类方法,判断方法有没RequestMapping修饰 * 4.如果有则把类+方法注解的路径拼接值和全限定方法名绑定到特定的HandlerMapping对应的map里
也就是说,一个请求路径通过HandlerMappings找到能处理这个请求的handle。找到这个handler后,让我们继续看handler怎么对这个请求进行处理的?根据流程图可知,我们拿到handler之后,会去匹配一个HandlerAdapter适配器,再通过这个映射器适配器去调用请求。那么我们就要看下这个HandlerAdapter适配器是个什么组件?
官网上对这个组件的描述并不是很多,只知道HandlerAdapter也是DispatchServlet内部的一个特殊Bean类型,作用是在适配器集合中给handler适配一个与handler匹配的适配器,最后通过adapter.handle()方法执行handler处理请求的逻辑。
也就是说,这里还有一个类似HandlerMappings集合功能的组件,这个组件就是HandlerAdapters适配器集合组件。源码如下:
在964行拿到handler后会在971行匹配一个合适的适配器adapter,匹配的过程就是通过handler在handlerAdapters集合中查找,如果满足条件ha.supports(handler) == true,则将匹配的adapter返回。实际上这个handlerAdapters集合与前面的HandlerMappings集合一样都是同样的初始化流程,具体有哪些handlerAdapter呢?通过源码或DispatcherServlet配置文件可看到一共有3种类型的Adapter:
从对HandlerMappings的学习,我们也可以大致推测出这3个handlerAdapter分别有什么作用。
(1)org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter:
使用http://localhost:9090/beanweb2请求,
可知如果handler的类型是HttpRequestHandler类型,那么最终会匹配到HttpRequestHandlerAdapter类型的adapter,例如本例中采用实现HttpRequestHandler接口类型的Controller,这种Controller注册产生的handler就由HttpRequestHandlerAdapter来调用handler处理。
(2)org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter:
使用http://localhost:9090/beanweb请求,
可知如果handler的类型是Controller类型,那么最终会匹配到SimpleControllerHandlerAdapter类型的adapter,例如本例中采用实现Controller接口类型的Controller,这种Controller注册产生的handler就由SimpleControllerHandlerAdapter来调用handler处理。
(3)org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter:
使用http://localhost:9090/web/hi?name=yang请求
[code]
如果handler方法是HandlerMethod类型来声明,那么最终会匹配到RequestMappingHandlerAdapter类型的adapter,例如本例中采用注解声明的Controller,这种方式最终是通过反射的方式来注册一个Controller,对于这种Controller注册产生的handler就由RequestMappingHandlerAdapter来调用handler处理。
问题来了?为什么SpringMVC要设计这些HandlerAdapter呢?
原因很简单,那就是为了代码的可复用,采用适配器模式让handler能兼容各种请求进行处理,因为不同写法Controller产生的handler处理请求的逻辑是不一样的。例如上面示例的三种handler,为了使代码具有很好的可阅读性和兼容性通过适配器模式,对不同的handler匹配一个对应的adapter,然后在这个匹配的adapter内执行对应的handler逻辑。
读完这部分源码,我们已经把通过请求,在HandlerMappings中查找对应handler,再通过handler在HandlerAdapters中找到适配的adapter,接下来就是通过adapter.handle()方法执行具体的handler处理请求逻辑了,核心代码在991行:
[code]mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
这个处理请求的核心方法最终会返回一个处理结果ModelAndView对象,记住这里有3种 Adapter类型自然会对应3种handle逻辑,我们不一一进行深入剖析,拿我们常用的@Controller注解方式的handle处理请求逻辑为例进行剖析,也就是RequestMappingHandlerAdapter.handler():
在看这个方法内部实现之前,我们知道通过@Controller注解方式实际是利用反射技术来调用请求处理逻辑。那么反射调用一个方法主要需要实例对象和参数。那么在调用handler方法时,很重要的一步就是从请求Request中获取到各种方法需要的参数,例如String,Onject,Request,Response等等不同类型的参数,这些参数怎么获取呢?对参数的获取SpringMVC设计了一个HandlerMethodArgumentResolver组件,专门用于对不同类型的参数进行参数处理获取操作,也类似于适配器模式。
上面获取参数的核心操作调用链方法:
invokeHandlerMethod-->invokeAndHandle(webRequest, mavContainer)
-->invokeForRequest(webRequest, mavContainer, providedArgs);
到达InvocableHandlerMethod类的invokeForRequest(webRequest, mavContainer, providedArgs):
获取参数和反射调用逻辑部分如下:
让我们继续看参数获取部分代码逻辑是怎么实现的?
[code] /** * Get the method argument values for the current request. */ private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //获取方法的数组 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; //遍历方法数组,对每个方法进行处理 for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); //先从缓存中根据方法名找对应的参数列表,如果缓存中存在则直接返回,不存在则下一步 args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } //拿着方法参数看在argumentResolvers适配器集合中有哪个Resolvers能处理这种方法参数 if (this.argumentResolvers.supportsParameter(parameter)) { try { //使用匹配的Resolvers参数处理器处理请求获取参数值数组 args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex); } throw ex; } } if (args[i] == null) { throw new IllegalStateException("Could not resolve method parameter at index " + parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() + ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i)); } } return args; }
通过这个方法的解读,我们可以知道关于参数的获取过程是:
(1)先获取方法数组;
(2)遍历方法数组,在缓存中找这个方法的参数列表,如果存在则返回缓存中的参数列表值;
(3)如果缓存不存在参数列表值,则拿着这个方法参数去一个叫argumentResolvers的参数处理器集合中匹配看有哪个Resolver能处理这种方法参数,如果匹配到了则交由这个参数处理器处理;
(4)匹配的参数处理器对特定的参数类型在请求Request中获取参数:如String参数则在Request中通过getParameter获取,Request参数则直接
到了这里,就不得不提及这个参数处理器HandlerMethodArgumentResolver,这是一个接口,提供了两个接口方法分别如下:
[code]public interface HandlerMethodArgumentResolver { boolean supportsParameter(MethodParameter parameter); @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; }
这两个接口方法这么设计的想法是什么呢?因为Request中会有很多种类型的参数,我们知道SpringMVC对每一种参数类型都设计了一个HandlerMethodArgumentResolver来专属处理。那么这么多参数就需要进行抽象,于是就抽象出这么一个公共接口,所有的参数处理器都需要实现这个接口。然后在boolean supportsParameter(MethodParameter parameter)中传入方法参数,不同的参数处理器只能处理指定的方法参数,因此对于一个处理器来说,当传入的方法参数是它能处理的那么这个方法就返回true,否则返回false;而resolveArgument方法实现就是从Request中真正获取具体参数的逻辑方法了。这两个方法配合使用就可以做到不同的参数进来,可以通过适配器模式来匹配正确的参数处理器进行参数获取。
HandlerMethodArgumentResolver参数处理器有非常多:
例如我们常用的对方法参数中加了@RequestParam绑定的参数,则是通过RequestParamMethodArgumentResolver这个参数处理器进行处理:
那这种类型的处理器处理参数的逻辑也可以看到是一个方法,有些参数SpringMVC是使用ASM对字节码进行操作得到参数值,具体实现逻辑不做深入了解:
[code]protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
关于怎么自定义一个HandlerMethodArgumentResolver,可以在实现了WebMvcConfigurer的AppConfig中的addArgumentResolvers方法内add(new 自定义Resolver)的方式生效,当然这里还涉及一个参数处理器的顺序,在Resolvers集合内会按顺序遍历合适的Resolver来处理,如果要让自定义的Resolver优先被遍历则需要进行特殊处理,在AppConfig中添加一个@Autoaired修饰的initArgumentResolvers方法,内部进行如下处理,也看到原来@Autoaired还可以用来修饰一个方法:
在Resolve获取了参数值之后,就进行反射调用,返回一个ModelAndView:
[code]mav = invokeHandlerMethod(request, response, handlerMethod);
到这一步就剩下视图解析器以及视图渲染输出的流程了。我们知道一个方法执行后可以返回一个String视图名或者一个ResponseBody的对象,那么我们就要了解SpringMVC是如何渲染视图,如何解析ResponseBody,以及如何将渲染后的结果View通过Writer写往浏览器客户端?
继续看上面的代码:mav = invokeHandlerMethod(request, response, handlerMethod);
在这个方法内,会先设置出两个组件argumentResolvers和returnValueHandlers后面使用,分别是参数处理器和结果处理器;
那么核心代码在877行:
[code]invocableMethod.invokeAndHandle(webRequest, mavContainer);
前面我们知道,调用完下面handler方法逻辑:
[code]Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs)
之后会返回一个返回值。那么一般这个返回值就会有下面3种情况,也就是Controller里写的3种类型方法:
[code]/** * @ResponseBody返回对象类型 * 由一个handler判断方法是否有@ResponseBody注解,如果有该注解, * 再看返回值类型是否为String是的话将数据作为字符串输出, * 如果为其它类型则尝试找一个能处理的消息转换器,由消息转换器协助处理返回值后输出 * @return */ @RequestMapping(value="/himap", method = RequestMethod.GET) @ResponseBody public Map sayHiMap(){ Map<String,String> map = new HashMap<String,String>(); map.put("name","yang"); return map; } /** * 不加@ResponseBody返回String类型页面,通过源码可知实际也会内部创建一个ModelAndView来封装 * @return */ @RequestMapping(value="/index", method = RequestMethod.GET) public String indexPage(){ return "index"; } /** * 返回ModelAndView类型 * @return */ @RequestMapping(value="/himav", method = RequestMethod.GET) public ModelAndView hiToMAV(){ ModelAndView mav = new ModelAndView(); mav.getModel().put("name","yang"); mav.setViewName("index"); //将index.jsp作为view放到mav中输出 return mav; }
因此Object returnValue就对应上面三种类型,如上面三个例子的返回值分别是HashMap,index字符串,ModelAndView对象:
下面紧接着一行setResponseStatus(webRequest); 这个方法里主要是对响应状态进行封装转化,可以给方法添加@ResponseStatus注解,使方法发生异常时错误信息以一种对用户友好的方式转化输出,对这个注解的应用可以参考下面另一篇文档https://www.geek-share.com/detail/2805546979.html
从上面可知,返回的returnValue != null,就会执行
[code]this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
这里其实也是适配器模式的设计,对于返回值类型的处理都是交给各个匹配的返回值处理适配器去处理。这里分别对上面三种返回值类型,找到对应的适配器,看这些适配器各自是怎么处理上面讲的三种返回值类型的?
(1) @ResponseBody返回对象类型,这种类型的返回值处理是一个后置处理器:
[code]RequestResponseBodyMethodProcessor
但是我们返回的是一个Map,最终会走260行内的逻辑,也就是去消息转换器集合中找一个合适的消息转换器对这个返回值进行处理。由于我们返回是Map,对于这种类型我们是采用了一个fastJson的自定义消息转换器来处理Map,这就从源码上解释了前一篇应用文章中对自定义消息转换器的配置和拓展,最终将Map对象通过Response.writer对象写出给浏览器:
输出给浏览器之后由于ModelAndView为null,后续的getModelAndView(mavContainer, modelFactory, webRequest); 也不会再执行。至此对于@ResponseBody返回对象类型的输出为止的流程就结束了,大概处理逻辑是:
由一个handler判断方法是否有@ResponseBody注解,如果有该注解, 再看返回值类型是否为String是的话将数据作为字符串数据输出, 如果为其它类型比如Map则尝试找一个能处理的消息转换器,由消息转换器协助处理返回值后输出(在本例子中我们拓展了一个fastJson的消息转换器专门用于协助处理Map类型的返回值)。
(2)String视图类型:这种返回值类型是页面名称返回值类型,由下面返回值处理器进行处理操作:
[code]ViewNameMethodReturnValueHandler
通过mavContainer.setViewName(viewName);将目标页面名称放入到viewName属性中,然后在后面方法内对这个viewName进行处理:
[code]getModelAndView(mavContainer, modelFactory, webRequest);
实际上,对于这种String视图类型的返回值,其内部也是通过ModelAndView进行封装的。这个方法执行结束后ModelAndView就返回给了DispatcheServlet,但是此时还没有对这个ModelAndView进行视图解析,真正的视图解析操作在DispatcheServlet的
[code]processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
我们先看一下这个方法的参数:
[code]private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception
可以发现,这个除了传入ModelAndView之外,还传人了一个Exception异常对象,为什么要传这个异常对象呢?继续往源码看:
从源码中我们可以明白,原来传入这个Exception异常对象,是因为如果有异常发生时,SpringMVC会为浏览器产生一个异常页面输出,这就是我们在浏览器看到的异常页面,并且是可以自己去定义发生什么类型的异常输出什么异常页面。
如果是没有异常的正常页面,则解析逻辑如下,很显然核心逻辑在render方法内:
在这个render方法里,主要分两步核心:第一步是根据ViewName通过视图解析器解析出静态文件资源URL;第二步就是将数据封装到Request域的Attrubute属性内通过Response.writer输出到浏览器。
第一步:
第一步通过viewName会去找合适的视图解析器,对这个viewName进行合适的解析产生具体的InternalResourceView出来。我们在本例中是配置了前缀和后缀的视图解析器,最终通过视图解析出来的URL就是要渲染给浏览器的/WEB-INF/jsp/index.jsp文件。
第二步:
至此对于String页面对象类型的视图解析和输出过程就结束了,大概处理逻辑是:
根据返回值类型是String的页面视图类型,去ViewNameMethodReturnValueHandler根据viewName找到对应的ModelAndView,找到后ModelAndView返回给DispatcheServlet,此时还没有对这个ModelAndView进行真正视图解析,然后由DispatcheServlet调用render方法,第一步根据ViewName通过视图解析器解析出静态文件资源URL,再第二步将数据和View封装到Request域的Attrubute属性内通过Response.writer输出到浏览器。
(3)对于ModelAndView返回类型:通过阅读了String页面返回类型的处理,理解ModelAndView类型的处理就会变得很简单。
这种返回类型的处理是由下面返回值处理器进行处理操作:
[code]ModelAndViewMethodReturnValueHandler
直接从Controller的方法里将model对象放入mavContainer中,返回给DispatcherServlet的就是方法内自己new出来并返回出去的ModelAndView。
。
然后后面的操作和String页面返回类型处理基本一致。两者的区别在于:
(1)两者使用的返回值类型处理器不一样:String页面类型使用ViewNameMethodReturnValueHandler,ModelAndView类型使用ModelAndViewMethodReturnValueHandler;
(2)String页面返回类型需要根据字符串去ViewNameMethodReturnValueHandler根据viewName产生一个ModelAndView,但是ModelAndView返回类型不需要,产生mav后面的流程为视图解析,参数和view封装到request这些操作基本是一致的。
至此对于ModelAndView返回类型的视图解析和输出过程大概处理逻辑比String页面类型少了前置产生mav的步骤,自身业务代码new出来的modelAndView交给DispatcheServlet调用render方法,第一步根据ViewName通过视图解析器解析出静态文件资源URL,再第二步将数据和View封装到Request域的Attrubute属性内通过Response.writer输出到浏览器。
由于全文流程较长,最后在这里进行全流程总结及文字归纳:
1:首先方法进入:doDispatch
2:checkMultipart() 判断当前请求是否有文件,如附件上传
3:getHandler() :通过HandleMapping去找一个Controller对象
3.1:扩展点1:HandleMapping
3.2: Spring boot 扩展Spring mvc 其中就扩展了 HandleMapping 去解析静态资源
4: getHandlerAdapter(): 根据你controller的类型去找一个适配器
4.1: 因为Controller有很多种不同的注册方式 所以需要不同的适配器
4.2:扩展点2:HandlerAdapter
5:handle() : 执行Controller逻辑并且进行视图裁决(判断是要重定向还是转发还是响应页面)
5.1:invokeForRequest():执行方法的全部逻辑
5.2:首先给参数赋值
5.2.1:参数赋值的扩展点:HandlerMethodArgumentResolver 参数处理器
5.3:调用invoke()执行handler实例的方法
6:setResponseStatus()设置ResponseStatus响应状态码,对应@ResponseStatus注解加在方法上将异常信息转化成友好输出
7:handleReturnValue() 进行视图裁决
7.1:扩展点:returnValueHandlers 通过这个对象来进行判断返回值类型由什么返回值处理器进行处理(直接输出还是产生mav)
8:handler.handleReturnValue()对应于返回值处理(判断是否需要响应还是需要重定向还是转发)
8.1: 如果是@ResponseBody 注解又有一个扩展点:HttpMessageConverter消息转换器
9:getModelAndView() 对DispatcheServlet给过来的mav重新封装一个ModelAndView对象,这里未做视图解析
9.1:如果不需要渲染视图(如果是重定向 || 响应视图的话) 就会返回null
9.2: mavContainer.isRequestHandled() 判断是否需要重定向或响应
9.3: 同时会把model里面的参数放到request.setAttribute(说明model的作用域是request作用域)
10:processDispatchResult():开始做视图解析和渲染
10.1:判断是否需要响应异常视图
10.2:扩展点:ViewResolver视图解析器得到包含资源文件URI的InternalResourceView对象
10.2:拿到视图名称封装一个视图对象,数据和视图对象封装到request域中
10.3:通过response进行forward/include跳转或重定向到资源文件(如果配置了mav的viewName带有redirect:则为重定向View)
.....
至此SpringMVC的流程源码大致解析完成,这里也附上SpringMVC早期版本关于父子容器的介绍,可参考如下文档:
- 深入剖析Spring Web源码(九) - 处理器映射,处理器适配器以及处理器的实现 - 基于注解控制器流程的实现
- SpringBoot学习 (四) - 自动配置原理 静态资源的映射规则 模板引擎 SpringMVC自动配置-扩展-全面接管
- 深入剖析Spring Web源码(八) - 处理器映射,处理器适配器以及处理器的实现 - 基于简单控制器流程的实现
- 深入剖析Spring Web源码(十) - 处理器映射,处理器适配器以及处理器的实现 - 基于HTTP请求处理器流程的实现
- SpringBoot系列<四>视图解析、路径-资源映射、静态资源访问
- spring的启动过程——spring和springMVC父子容器的原理
- 深入剖析Spring Web源码(十三) - 处理器映射,处理器适配器以及处理器的实现 - 处理器的实现架构 - 简单控制器
- SpringWeb MVC处理请求的流程:(处理器映射器、处理器适配器、视图解析器称为springmvc的三大组件)
- Spring IOC源码分析(七):IOC容器的设计实现与bean对象的创建流程
- SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?
- Spring源码解读-Spring IoC容器初始化之资源解析
- spring和springMVC父子容器的原理
- Spring 源码深入解析(1)之bean容器的基本实现(二)
- spring的启动过程——spring和springMVC父子容器的原理
- Spring Boot -- 关于创建了springmvc的配置类导致静态资源访问失效解决方法
- SpringMVC源码深度解析之SpringServletContainerInitializer原理分析
- java架构之路-(SpringMVC篇)SpringMVC主要流程源码解析(下)注解配置,统一错误处理和拦截器
- 【SpringMVC系列一】 EasyUI + SpringMVC 解决静态资源访问
- 【Spring源码--IOC容器的实现】(三)BeanDefinition的载入和解析【I】
- 【Spring源码--IOC容器的实现】(三)BeanDefinition的载入和解析【II】