一、九大组件

  1. HandlerMapping(处理器映射器)

HandlerMapping 是⽤来查找Handler的,也就是处理器,具体的表现形式可以是类,也可以是⽅法。⽐如,标注了@RequestMapping的每个⽅法都可以看成是⼀个Handler。Handler负责具 体实际的请求处理,在请求到达后,HandlerMapping 的作⽤便是找到请求相应的处理器Handler 和 Interceptor.

  1. HandlerAdapter(处理器适配器)

HandlerAdapter 是⼀个适配器。因为 SpringMVC 中 Handler 可以是任意形式的,只要能处理请求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的⽅法结构都是doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理⽅法调⽤ Handler 来进⾏处理,便是 HandlerAdapter 的职责。

注意:因为RequestMappingHandlerAdapter实现了 InitializingBean接口,所以在其初始化之后,会调用afterPropertiesSet()。在该方法中对spring默认的方法参数解析器,方法返回值解析器,初始化绑定器进行了设置的

  • 参数解析器
  • 返回值处理器
  1. HandlerExceptionResolver

HandlerExceptionResolver ⽤于处理 Handler 产⽣的异常情况。它的作⽤是根据异常设置 ModelAndView,之后交给渲染⽅法进⾏渲染,渲染⽅法会将 ModelAndView 渲染成⻚⾯。

  1. ViewResolver

ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图,只有⼀ 个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名viewName 最终会在这⾥被解析成为View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver在这个过程主要完成两件事情: ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个InternalResourceViewResolver,是针对 JSP 类型视图的。

  1. RequestToViewNameTranslator

RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,便要通过这个组件从请求中查找 ViewName。

  1. LocaleResolver

ViewResolver组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这个组件也是 i18n 的基础。

  1. ThemeResolver

ThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。 Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名, ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和具体的资源。

  1. MultipartResolver

MultipartResolver ⽤于上传请求,通过将普通的请求包装成MultipartHttpServletRequest 来实现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还可以调⽤ getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就是封装普通的请求,使其拥有⽂件上传的功能。

  1. FlashMapManager

FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然 可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得呢?因为重定向没有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通 过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过 ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。


这一节参考文章

二、SpringMVC的一次请求

我们先来看一下入口在哪。众所周知,Servlet标准定义了所有请求先由service方法处理,如果是get或post方法,那么再交由doGet或是doPost方法处理

FrameworkServlet覆盖了service方法:

@Override
protected void service(HttpServletRequest request, HttpServletResponse response) {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (HttpMethod.PATCH == httpMethod || httpMethod == null) {processRequest(request, response);} else {super.service(request, response);}
}

Spring要覆盖此方法的目的在于拦截PATCH请求,PATCH请求与PUT类似,不同在于PATCH是局部更新,而后者是全部更新。FrameworkServlet同样也覆盖了doGet和doPost方法,两者只是调用processRequest方法。

  1. 请求上下文
  • processRequest方法

它首先备份了请求的LocaleContext和RequestAttributes,然后copy了一份新的,并绑定到当前线程

//将地区(Locale)和请求属性以ThreadLocal的方法与当前线程进行关联,分别可以通过LocaleContextHolder和RequestContextHolder进行获取。
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);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);

initContextHolders就是将localeContext和requestAttributes两个对象存入Holder。并且在方法最后,将备份的数据恢复过来,并触发请求处理完毕事件:

finally {resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);publishRequestHandledEvent(request, response, startTime, failureCause);
}

备份和绑定操作完成后,调用它的核心方法doService,这是一个抽象方法,在DispatcherServlet实现,首先也是备份属性,并且最后也进行了恢复

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);// 如果该请求是include的请求(请求包含) 那么就把request域中的数据保存一份快照版本// 等doDispatch结束之后,会把这个快照版本的数据覆盖到新的request里面去Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap<>();Enumeration<?> attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// 把一些常用对象放进请求域,方便Handler里面可以随意获取// doService将webApplication、localeResolver、themeResolver、ThemeSource、outputFlashMap和flashMapManage设置到request的属性中,// 请求分发。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());// 如果是重定向,放置得更多一些,比如flashMapManagerif (this.flashMapManager != null) {FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);}try {//然后将请求传入到doDispatch中,DispatcherServlet最重要的方法,交给他去分发请求、找到handler处理等等doDispatch(request, response);}finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}}
  • DispatcherServlet.doDispatch源码:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 此处用processedRequest  需要注意的是:若是处理上传,processedRequest 将和request不再指向同一对象HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;//主要用来管理异步请求的处理。什么时候要用到异步处理呢?就是业务逻辑复杂(或者其他原因),为了避免请求线程阻塞,需要委托给另一个线程的时候。WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {//检查是否为上传文件请求//checkMultipart 这个方法很重要,判断是否是上传需求。且看下面的具体分析://如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求processedRequest = checkMultipart(request);//标记一下:是否是文件上传的requestmultipartRequestParsed = (processedRequest != request);// 步骤1,获取执行链,重要(也就是找到某个controller中的某个方法)// 找到一个处理器,如果没有找到对应的处理类的话,这里通常会返回404,如果throwExceptionIfNoHandlerFound属性值为true的情况下会抛出异常mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 步骤2,获取适配器,一般都是返回RequestMappingHandlerAdapter(用于处理@RequestMapping注解的方法)// 根据实际的handler去找到一个合适的HandlerAdapter,方法详细逻辑同getHandler,因此不再解释HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//如果是GET请求,如果内容没有变化的话,则直接返回,用于响应304状态码String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {//在客户端地一次输入URL时,服务器端会返回内容和状态码200, 表示请求成功,同时会添加一个“Last-Modified”属性,表示该请求资源的最后修改时间//客户端第二次请求此URL时,客户端会向服务器发送请求头 “IF-Modified-Since”,//如果服务端内容没有变化,则自动返回HTTP304状态码(只返回相应头信息,不返回资源文件内容,这样就可以节省网络带宽,提供响应速度和用户体验)long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}// 步骤3,拦截器pre方法,重要// 这段代码很有意思:执行处理器连里的拦截器们,具体参阅下面详细:if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}//步骤4,执行目标方法,真正处理逻辑,重要// 真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndView// 这也是一个很复杂的过程(序列化、数据绑定等等),需要后面专题讲解// 这里一般最后都是由RequestMappingHandlerAdapter的invocableMethod.invokeHandle执行// 参数解析和返回值处理都在这个方法执行// 如果是@ResponseBody的方法,那么mv=nullmv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的if (asyncManager.isConcurrentHandlingStarted()) {return;}//意思是:如果我们没有设置viewName,就采用默认的。否则采用我们自己的applyDefaultViewName(processedRequest, mv);//步骤5,拦截器post方法,重要// 执行所有的拦截器的postHandle方法,并且把mv给他// 这里有一个小细节:这个时候拦截器是【倒序】执行的mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}//步骤6,处理视图,重要//这个方法很重要,顾名思义,他是来处理结果的,渲染视图、处理异常等等的  下面详细分解//如果是@ResponseBody,那么这个方法里面的逻辑除了拦截器的逻辑,其他都不会执行processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {//步骤7,拦截器收尾方法,重要triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}
  • checkMultipart
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {// 只有配置了multipartResolver,并且是文件上传的请求,才会继续往下走,没有的话就可以不必继续了。if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {// 如果是文件上传请求,则继续判断这个请求是不是已经被转换为MultipartHttpServletRequest类型了// 如果该请求已经是MultipartHttpServletRequest 那就输出一个日志走人//在Spring-Web这个jar中有一个过滤器org.springframework.web.multipart.support.MultipartFilter//如果在web.xml中配置这个过滤器的话,则会在过滤器中提前判断是不是文件上传的请求,//并将请求转换为MultipartHttpServletRequest类型。if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");}}else if (hasMultipartException(request)) {logger.debug("Multipart resolution previously failed for current request - " +"skipping re-resolution for undisturbed error rendering");}else {try {// 这里特别注意,不管是哪种multipartResolver的实现,内部都是new了一个新的MultipartHttpServletRequest的实现类,// 所以不再指向原来的request了,所以一定要注意// 将请求转换为MultipartHttpServletRequest类型return this.multipartResolver.resolveMultipart(request);}catch (MultipartException ex) {if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {logger.debug("Multipart resolution failed for error dispatch", ex);// Keep processing error dispatch with regular request handle below}else {throw ex;}}}}// If not returned before: return original request.// 如果前面没有返回,就原样返回,相当于啥都不做return request;}

更多关于文件上传处理的debug参考这篇文章

  1. 处理器查找:为此请求返回HandlerExecutionChain

即为请求寻找合适的Controller的过程。DispatcherServlet.getHandler:

protected HandlerExecutionChain getHandler(HttpServletRequest request) {for (HandlerMapping hm : this.handlerMappings) {HandlerExecutionChain handler = hm.getHandler(request);if (handler != null) {return handler;}}return null;
}

从这里可以看出,寻找处理器实际上委托给HandlerMapping实现,寻找的过程便是遍历所有的HandlerMapping进行查找,一旦找到,那么不再继续进行遍历。也就是说HandlerMapping之间有优先级的概念,而根据AnnotationDrivenBeanDefinitionParser的注释,RequestMappingHandlerMapping其实有最高的优先级。

AbstractHandlerMapping.getHandler:

@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {Object handler = getHandlerInternal(request);HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);//判断请求头中是否有ORIGIN字段if (CorsUtils.isCorsRequest(request)) {CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;
}

getHandlerInternal方法便是根据url进行查找的过程。下面重点是执行链的生成。getHandlerExecutionChain方法的原理就是从adaptedInterceptors中获得所有可以适配当前请求URL的MappedInterceptor并将其添加到HandlerExecutionChain的拦截器列表中。拦截器的顺序其实就是我们定义/注册的顺序。从getCorsHandlerExecutionChain的源码中可以看出,对于跨域请求其实是向调用链插入了一个CorsInterceptor。

  1. 适配器查找:一般都是返回RequestMappingHandlerAdapter(用于处理@RequestMapping注解的方法),根据实际的handler去找到一个合适的HandlerAdapter,方法详细逻辑同getHandler
  • DispatcherServlet.getHandlerAdapter:
protected HandlerAdapter getHandlerAdapter(Object handler) {for (HandlerAdapter ha : this.handlerAdapters) {if (ha.supports(handler)) {return ha;}}
}

第一个适配器是RequestMappingHandlerAdapter,而其support方法直接返回true,这就导致了使用的适配器总是这一个

  • 更多适配器

  1. SimpleControllerHandlerAdapter:适配SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping的映射的,也就是实现Controller接口的Handler
  2. AbstractHandlerMethodAdapter:适配RequestMappingHandlerMapping,也就是我们常用的RequestMapping注解
  3. HttpRequestHandlerAdapter :适配远程调用的
  4. SimpleServletHandlerAdapter:适配Servlet实现类的
  • supports
 public final boolean supports(Object handler) {return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));}
  • supportsInternal
    //总是返回true ,因为任何方法参数和返回值类型会以某种方式加以处理。//不被任何HandlerMethodArgumentResolver识别的方法参数被解释为一个请求参数,如果它是一个简单的类型,或者作为模型属性否则。//没有任何HandlerMethodReturnValueHandler识别的返回值将被解释为一个模型属性protected boolean supportsInternal(HandlerMethod handlerMethod) {return true;}

如果是GET请求,内容没有变化则直接返回

                    //如果是GET请求,如果内容没有变化的话,则直接返回,用于响应304状态码String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {//在客户端地一次输入URL时,服务器端会返回内容和状态码200, 表示请求成功,同时会添加一个“Last-Modified”属性,表示该请求资源的最后修改时间//客户端第二次请求此URL时,客户端会向服务器发送请求头 “IF-Modified-Since”,//如果服务端内容没有变化,则自动返回HTTP304状态码(只返回相应头信息,不返回资源文件内容,这样就可以节省网络带宽,提供响应速度和用户体验)long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}
  • applyPreHandle
// 这段代码很有意思:执行处理器连里的拦截器们,具体参阅下面详细:
// 在Servlet规范中,设计了filter组件,可以在每个Web请求前后对它做处理,显然这种处理粒度太粗。Spring MVC增加了拦截器的概念,从HandlerMapping初始化和Handler查找的过程中,我们可以看到它的身影。
// 拦截器接口HandlerInterceptor定义了三个方法:preHandle、postHandle、afterCompletion,分别作用于处理器方法调用前后、处理器执行链全部执行后。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];// 注意:如果是拦截器返回了false,就立马触发所有拦截器的AfterCompletion 方法。并且马上return falseif (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}}return true;}
  1. 请求处理:mv=ha.handle()
//步骤4,执行目标方法,真正处理逻辑,重要
// 真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndView
// 这也是一个很复杂的过程(序列化、数据绑定等等),需要后面专题讲解
// 这里一般最后都是由RequestMappingHandlerAdapter的invocableMethod.invokeHandle执行
// 参数解析和返回值处理都在这个方法执行
// 并返回一个ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的
if (asyncManager.isConcurrentHandlingStarted()) {return;}
  • 以处理有@PathVariable注解的方法为例

因为在 getHandlerAdapter中已经返回了一个适配器RequestMappingHandlerAdapter,那么其调用链如下:

  1. mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
  2. RequestMappingHandlerAdapter.handerInternal
  3. RequestMappingHandlerAdapter.invokeHanderMethod
  4. ServletInvocableHandlerMethod.invokeAndHandle
  5. ServletInvocableHandlerMethod.invokeForRequest
  6. InvocableHandlerMethod.invokeForRequest
  7. InvocableHandlerMethod.getMethodArgumentValues
  8. InvocableHandlerMethod.resolveArgument
  9. HandlerMethodArgumentResolverComposite.resolveArgument
  10. AbstractNameedValueMethodArgumentResolver.resolveName
  11. PathVariableMethodArgumentResolver.resolveName (如果解析的是@PathVariable注解的参数)
  • RequestMappingHandlerAdapter.handleInternal:
@Override
protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod){ModelAndView mav;// Execute invokeHandlerMethod in synchronized block if required.// Session同步:可以看出,如果开启了synchronizeOnSession,那么同一个session的请求将会串行执行,//这一选项默认是关闭的,当然我们可以通过注入的方式进行改变。if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) {mav = invokeHandlerMethod(request, response, handlerMethod);}} else {// No HttpSession available -> no mutex necessarymav = invokeHandlerMethod(request, response, handlerMethod);}} else {// No synchronization on session demanded at all...mav = invokeHandlerMethod(request, response, handlerMethod);}if (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav;
}

总之,在mv = ha.handle(processedRequest, response, mappedHandler.getHandler());中,会将方法参数进行解析,执行该方法,并封装一个ModelAndView返回

这一节可以参考这篇文章

这篇文章清楚说明了,因为RequestMappingHandlerAdapter实现了 InitializingBean接口,所以在其初始化之后,会调用afterPropertiesSet()。

@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beans//首先执行此操作,它可能会添加 ResponseBody 建议 beaninitControllerAdviceCache();if (this.argumentResolvers == null) {//初始化SpringMVC默认的方法参数解析器,并添加至argumentResolvers(HandlerMethodArgumentResolverComposite)List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.initBinderArgumentResolvers == null) {//初始化SpringMVC默认的初始化绑定器(@InitBinder)参数解析器,并添加至initBinderArgumentResolvers(HandlerMethodArgumentResolverComposite)List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {//获取默认的方法返回值解析器List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}}

进一步的,我们可以看到spring默认的方法参数解析器,方法返回值解析器,初始化绑定器都是在这个方法中设置的

  • 同时我们也知道,HandlerAdapter内部含有一组解析器负责对各类型的参数进行解析(策略模式)。下面我们就再以常用的自定义参数和Model为例进行说明。

    • 自定义参数
      

解析由RequestParamMethodArgumentResolver完成。

supportsParameter方法决定了一个解析器可以解析的参数类型,该解析器支持@RequestParam标准的参数或是简单类型的参数,具体参见其注释。为什么此解析器可以同时解析@RequestParam注解和普通参数呢?玄机在于RequestMappingHandlerAdapter方法在初始化参数解析器时其实初始化了两个RequestMappingHandlerAdapter对象,getDefaultArgumentResolvers方法相关源码:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));// Catch-allresolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
}

useDefaultResolution参数用于启动对常规类型参数的解析,这里的常规类型指的又是什么呢?

实际上由BeanUtils.isSimpleProperty方法决定:

public static boolean isSimpleProperty(Class<?> clazz) {Assert.notNull(clazz, "Class must not be null");return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
}
public static boolean isSimpleValueType(Class<?> clazz) {return (ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() ||CharSequence.class.isAssignableFrom(clazz) ||Number.class.isAssignableFrom(clazz) ||Date.class.isAssignableFrom(clazz) ||URI.class == clazz || URL.class == clazz ||Locale.class == clazz || Class.class == clazz);
}

忽略复杂的调用关系,最核心的实现位于resolveName方法,部分源码:

@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {if (arg == null) {String[] paramValues = request.getParameterValues(name);if (paramValues != null) {arg = (paramValues.length == 1 ? paramValues[0] : paramValues);}}return arg;
}

name就是方法的参数名,可以看出,参数解析就是根据参数名去request查找对应属性的过程,在这里参数类型并没有起什么作用。参数名是从哪里来的?方法名获取的入口位于RequestParamMethodArgumentResolver的resolveArgument方法:

@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
}

getNamedValueInfo方法最终完成对MethodParameter的getParameterName方法的调用:

public String getParameterName() {ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer;if (discoverer != null) {String[] parameterNames = (this.method != null ?discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor));if (parameterNames != null) {this.parameterName = parameterNames[this.parameterIndex];}this.parameterNameDiscoverer = null;}return this.parameterName;
}

显然,参数名的获取由接口ParameterNameDiscoverer完成:

默认采用DefaultParameterNameDiscoverer,但此类其实相当于StandardReflectionParameterNameDiscoverer和LocalVariableTableParameterNameDiscoverer的组合,且前者先于后者进行解析。

  • StandardReflectionParameterNameDiscoverer.getParameterNames:
@Override
public String[] getParameterNames(Method method) {Parameter[] parameters = method.getParameters();String[] parameterNames = new String[parameters.length];for (int i = 0; i < parameters.length; i++) {Parameter param = parameters[i];if (!param.isNamePresent()) {return null;}parameterNames[i] = param.getName();}return parameterNames;
}
  • Model型参数解析

解析由ModelMethodProcessor完成。supportsParameter方法很简单:

@Override
public boolean supportsParameter(MethodParameter parameter) {return Model.class.isAssignableFrom(parameter.getParameterType());
}

很直白了。resolveArgument:

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {return mavContainer.getModel();
}

忽略各种调用关系,Model其实是一个BindingAwareModelMap对象,且每次请求(需要注入Model的前提下)都有一个新的该对象生成。类图:

  • 参数解析总结
  • 我们可以通过实现HandlerMethodArgumentResolver接口并将其注册容器的方式实现自定义参数类型的解析
  • 为了防止出现参数名获取不到的问题,应优先使用@RequestParam注解直接声明需要的参数名称。
  • 返回值解析

返回值解析开始于ServletInvocableHandlerMethod.invokeAndHandle.invokeForRequest之后

//调用方法并通过其中一个处理返回值//ServletInvocableHandlerMethodpublic void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);//与@ResponseStatus相关的处理setResponseStatus(webRequest);//进行返回值处理if (returnValue == null) {// Request的NotModified为true有@ResponseStatus注解标注 RequestHandled=true 三个条件有一个成立,则设置请求处理完成并返回if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);// 设置该请求已被处理mavContainer.setRequestHandled(true);return;}}//返回值不为null,@ResponseStatus存在reason 同样设置请求处理完成并返回else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}// 前边都不成立,则设置RequestHandled=false即请求未完成// 继续交给HandlerMethodReturnValueHandlerComposite处理mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");//利用所有返回值处理器来处理返回值try {//处理返回值this.returnValueHandlers.handleReturnValue(returnValue,//获取返回值类型,并传入到方法中getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;}}
  • 以标注了@ResponseBody的场景为例子
@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {//利用返回值,和返回值类型,选择适合的返回值处理器HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());}//利用得到的返回值处理器对返回值进行处理//HandlerMethodReturnValueHandler.handleReturnValuehandler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}@Nullableprivate HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {//先判断是不是异步返回值,遍历循环找到AsyncHandlerMethodReturnValueHandler进行判断boolean isAsyncValue = isAsyncReturnValue(value, returnType);//遍历returnValueHandlersfor (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {continue;}//判断当前HandlerMethodReturnValueHandler是否能够支持当前返回值的解析//HandlerMethodReturnValueHandler.supportsReturnType//例子:如果标了@ResponseBody,那么利用RequestResponseBodyMethodProcessor进行解析if (handler.supportsReturnType(returnType)) {return handler;}}return null;}
  • 进入RequestResponseBodyMethodProcessor进行处理
//RequestResponseBodyMethodProcessorpublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage = createInputMessage(webRequest);ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// Try even with null return value. ResponseBodyAdvice could get involved.// 利用消息转换器来进行处理writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}

这里又涉及到一个知识点,消息转换器Converter:通俗的理解就是看能不能将Class对象转为MediaType类型的数据(包括写入写出)。这里只展示部分实现类:

了解更多请参考

而在应该消息转换器前,还会有一步与浏览器与服务器内容协商的过程,比如浏览器能接受json类型的,服务器也能接受json类型的,那么就可以以json的类型发送数据,然后应用消息转换器,将返回值转成json数据

  • writeWithMessageConverters
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {//用于接收Controller返回值Object body;//声明接收返回对象类型Class<?> valueType;Type targetType;if (value instanceof CharSequence) {body = value.toString();valueType = String.class;targetType = String.class;}else {body = value;valueType = getReturnValueType(body, returnType);targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());}if (isResourceType(value, returnType)) {outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&outputMessage.getServletResponse().getStatus() == 200) {Resource resource = (Resource) value;try {List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());body = HttpRange.toResourceRegions(httpRanges, resource);valueType = body.getClass();targetType = RESOURCE_REGION_LIST_TYPE;}catch (IllegalArgumentException ex) {outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());}}}// 选择使用的 MediaTypeMediaType selectedMediaType = null;MediaType contentType = outputMessage.getHeaders().getContentType();if (contentType != null && contentType.isConcrete()) {if (logger.isDebugEnabled()) {logger.debug("Found 'Content-Type:" + contentType + "' in response");}selectedMediaType = contentType;}else {HttpServletRequest request = inputMessage.getServletRequest();// 获取浏览器能接受的MediaType(Accept-Type)List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);// 服务器能产生的MediaType(即@RequeMapping中指定的produces)List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}//声明匹配上的MediaTypesList<MediaType> mediaTypesToUse = new ArrayList<>();//循环匹配,协商MediaTypefor (MediaType requestedType : acceptableTypes) {for (MediaType producibleType : producibleTypes) {if (requestedType.isCompatibleWith(producibleType)) {mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));}}}//没匹配上MediaTypes就报错if (mediaTypesToUse.isEmpty()) {if (body != null) {throw new HttpMediaTypeNotAcceptableException(producibleTypes);}if (logger.isDebugEnabled()) {logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);}return;}MediaType.sortBySpecificityAndQuality(mediaTypesToUse);for (MediaType mediaType : mediaTypesToUse) {if (mediaType.isConcrete()) {selectedMediaType = mediaType;break;}else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;break;}}if (logger.isDebugEnabled()) {logger.debug("Using '" + selectedMediaType + "', given " +acceptableTypes + " and supported " + producibleTypes);}}//如果匹配到,则进行写入逻辑if (selectedMediaType != null) {//移除 quality 。例如,application/json;q=0.8 移除后为 application/jsonselectedMediaType = selectedMediaType.removeQualityValue();//遍历 messageConverters 数组for (HttpMessageConverter<?> converter : this.messageConverters) {//判断 HttpMessageConverter 是否支持转换目标类型GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ?((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {//拿到我们要响应的内容body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(),inputMessage, outputMessage);//body 非空,则进行写入if (body != null) {Object theBody = body;LogFormatUtils.traceDebug(logger, traceOn ->"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");//加头部addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {//调用消息转换器的write方法,将数据写出去genericConverter.write(body, targetType, selectedMediaType, outputMessage);}else {((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);}}else {if (logger.isDebugEnabled()) {logger.debug("Nothing to write: null body");}}// return 返回,结束整个逻辑return;}}}

这里有一点需要注意:使用@ResponseBody注解的类,不会经过视图解析,因为

//在使用@ResponseBody,mv=null
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

更多关于@ResponseBody的分析,参考

至此,一次@ResponseBody的解析就完成了,但我们也知道,springMVC也有视图解析的方式,直接给用户返回一个渲染后的页面,只不过在前后分离的趋势下,越来越少用

  • 返回页面的逻辑

套路和上面是一样的,通常情况,我们返回的其实是view名,只不过返回的负责处理的returnValueHandlers是ViewNameMethodReturnValueHandler,supportsReturnType方法:

@Override
public boolean supportsReturnType(MethodParameter returnType) {Class<?> paramType = returnType.getParameterType();return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {if (returnValue instanceof CharSequence) {String viewName = returnValue.toString();mavContainer.setViewName(viewName);// 判断的依据: 是否以redirect:开头if (isRedirectViewName(viewName)) {mavContainer.setRedirectModelScenario(true);}}
}

可见这里并没有进行实际的处理,只是解析得到了最终的视图名称。

  1. 目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
  2. 方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
  3. 任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)

bash这次返回的mv就不再是null了,就可以进行视图解析了

  1. applyPostHandle:执行拦截器post方法
//意思是:如果我们没有设置viewName,就采用默认的。否则采用我们自己的
applyDefaultViewName(processedRequest, mv);
// 步骤5,拦截器post方法,重要
// 执行所有的拦截器的postHandle方法,并且把mv给他
// 这里有一个小细节:这个时候拦截器是【倒序】执行的
mappedHandler.applyPostHandle(processedRequest, response, mv);
  1. 视图渲染:processDispatchResult进行处理结果,渲染视图、处理异常等等
  • 由DispatcherServlet的processDispatchResult方法完成,源码:
    //exception 执行处理器方法报错时被捕获的异常//如果是@ResponseBody,那么这个方法里面的逻辑除了拦截器的逻辑,其他都不会执行private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;//如果有异常,就进入异常处理逻辑,返回到异常页面if (exception != null) {// 含有异常页面视图的异常if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);//1、会执行所有的我们的自己配置(或者默认配置)了的HandlerExceptionResolver处理器//2、上面需要注意了,但凡处理方法返回的不是null,有mv的返回。那后面的处理器就不会再进行处理了。具有短路的效果,一定要注意  是通过null来判断的//3、处理完成后,得到error的视图mv,最后会设置一个viewName,然后返回出去mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// Did the handler return a view to render?// 如果mv不为空,且没有被清理,也就是请求在此之前还没有响应浏览器,就开始执行render()方法,开始渲染视图了if (mv != null && !mv.wasCleared()) {render(mv, request, response);// 如果有错误视图,这里清除掉所有的请求域里的所有的错误属性if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isTraceEnabled()) {logger.trace("No view rendering, null ModelAndView returned.");}}//处理异步=========我们发现,它不执行后面的AfterCompletion方法了,注意一下即可if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}// 执行拦截器的AfterCompletion 方法if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, null);}}

可以看出,处理根据是否抛出异常分为了两种情况

如果抛出了异常,那么processHandlerException方法将会遍历所有的HandlerExceptionResolver实例,默认有哪些参考MVC初始化-HandlerExceptionResolver检查一节。默认的处理器用于改变响应状态码、调用标注了@ExceptionHandler的bean进行处理,如果没有@ExceptionHandler的bean或是不能处理此类异常,那么就会导致ModelAndView始终为null,最终Spring MVC将异常向上抛给Tomcat,然后Tomcat就会把堆栈打印出来。

如果我们想将其定向到指定的错误页面,可以这样配置:

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="defaultErrorView" value="error"></property>
</bean>
  • DispatcherServlet.render()
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine locale for request and apply it to the response.// 通过localeResolver吧local解析出来,放到response里面去Locale locale =(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());response.setLocale(locale);//==================视图:关键中的关键==================View view;//获取视图名称String viewName = mv.getViewName();// 如果已经有viewName了(绝大多数情况)if (viewName != null) {// We need to resolve the view name.// 视图解析器  根据String类型的名字,解析出来一个视图(视图解析器有多个)// 还是那个原理:只要有一个返回了不为null的,后面的就不会再解析了// 根据我们的视图名称 解析成为我们真正的物理视图(通过视图解析器对象)view = resolveViewName(viewName, mv.getModelInternal(), locale, request);// 如果解析不出来视图,那就抛出异常,说不能解析该视图if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() +"' in servlet with name '" + getServletName() + "'");}}else {//没有视图名称,但是必须有视图内容,否则抛出异常// No need to lookup: the ModelAndView object contains the actual View object.view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");}}// Delegate to the View object for rendering.if (logger.isTraceEnabled()) {logger.trace("Rendering view [" + view + "] ");}try {//设置响应码 statusif (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}// 根据model里的数据,正式渲染(关于此部分逻辑,后续再说,也比较复杂)view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "]", ex);}throw ex;}}
  • DispatcherServlet.resolveViewName()
//解析viewName返回一个View对象
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,Locale locale, HttpServletRequest request) throws Exception {//判断当前的视图解析器集合是否为空if (this.viewResolvers != null) {//循环调用我们的视图解析器对象解析视图for (ViewResolver viewResolver : this.viewResolvers) {// 一旦有我们的视图解析器能够解析出视图,后面的视图解析器不在参与解析,直接返回View view = viewResolver.resolveViewName(viewName, locale);if (view != null) {return view;}}}return null;}

resolveViewName方法将会遍历所有的ViewResolver bean,只要有一个解析的结果(View)不为空,即停止遍历。根据MVC初始化-ViewResolver检查一节和我们的配置文件可知,容器中有两个ViewResolver ,分别是: InternalResourceViewResolver和UrlBasedViewResolver。resolveViewName其实只做了一件事: 用反射创建并初始化我们指定的View,根据我们的配置,就是JstlView

  • processDispatchResult :处理派发结果(小结,以redirect为例
  • render(mv, request, response); 进行页面渲染逻辑
  1. 根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
    ■ 1、所有的视图解析器尝试是否能根据当前返回值得到View对象
    ■ 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
    ■ 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
    ■ 4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
    4.1 RedirectView如何渲染【重定向到一个页面】
    4.2 获取目标URL地址
    4.3 response.sendRedirect(encodeURL)
  • 视图解析小结:
  • 返回值有异常: 遍历所有的HandlerExceptionResolver实例进行处理,或者向上抛给Tomcat
  • 返回值以 forward: 开始: new InternalResourceView(forwardUrl); -----> 转发request.getRequestDispatcher(path).forward(request, response);
  • 返回值以 redirect: 开始: new RedirectView() —> render就是重定向
  • 返回值是普通字符串:new ThymeleafView()[假设是使用Thymeleaf模板引擎]—>
    此处理器会返回一个非空的ModelAndView。然后使用writer将数据写入到前端

我们可以自定义视图解析器+自定义视图

  1. ModelAndView

回过头来看一下这到底是个什么东西。类图: ModelAndView类图

很直白。

怎么生成的。RequestMappingHandlerAdapter.getModelAndView相关源码:

ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
  1. ViewResolver

resolveViewName方法的源码不再贴出,其实只做了一件事: 用反射创建并初始化我们指定的View,根据我们的配置,就是JstlView

渲染的核心逻辑位于InternalResourceView.renderMergedOutputModel,简略版源码:

@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {// 将Model中的属性设置的request中exposeModelAsRequestAttributes(model, request);// 获取资源(jsp)路径String dispatcherPath = prepareForRendering(request, response);// Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);// If already included or response already committed, perform include, else forward.if (useInclude(request, response)) {response.setContentType(getContentType());rd.include(request, response);} else {// Note: The forwarded resource is supposed to determine the content type itself.rd.forward(request, response);}
}

可以看出,对jsp来说,所谓的渲染其实就是将Model中的属性设置到Request,再利用原生Servlet RequestDispatcher API进行转发的过程。

  1. Model、View、ModelAndView对比
  • View为服务器上的某个文件容器`,可以为JSP,FTL等动态页面文件,甚至是媒体文件等等,单单是一个文件。

  • Model的作用是存储动态页面属性,动态页面文件即View可以在Model中获取动态数据,Model其实是一个ModelMap类型,它是一个LinkedHashMap的子类,作用类似于request对象的setAttribute方法的作用(数据在一次请求转发中有效),用来在一个请求过程中传递处理的数据。

  • ModelAndView,顾名思义,就是整合了Model和View,常用于动态页面,一个后台网页就包含这两部分,前台就是基本的html代码

  • 可以参考该文章

  1. 总结图

其实,在如今的前后端分离的浪潮下,试图解析这一流程也是越来越少,大部分都是返回数据供前端渲染

参考文章
参考文章

springMVC九大组件及一次请求流程相关推荐

  1. springmvc十六:九大组件

    DispatcherServlet中有九个引用类型的属性,这就是springmvc的九大组件. springmvc在工作的时候,关键位置都是由这些组件完成的. /** MultipartResolve ...

  2. Openstack九大组件

    云是服务 云,本身没有资源,只是去管理和调度资源 虚拟化是技术 虚拟化提供资源,在云的世界里,虚拟化提供底层的计算.存储.网络资源,由云来接管 云的五大特征: 按需自助服务 广泛的网络接入 资源池化 ...

  3. 【springmvc】九大组件之HandlerExceptionResolver

    在Spring MVC中,如果对异常不做任何处理,Spring MVC会将异常直接抛给容器. 例如下面的代码抛出了异常: @GetMapping("e1") public Stri ...

  4. springmvc源码阅读3--dispatcherServlet reqeust的执行流程

    一.前言 太多的时候总是会遇到群里面问,报404怎么肥事呀,前台的参数怎么后台收不到呀--,上次就在群里面遇到过,围绕这一个点:input的name值是不是错了呀,人家妹子都截了好几次图说没有问题,然 ...

  5. ajax请求方式 问答题,java spring mvc面试题,九大常见问答题

    上次已经为大家介绍过java spring面试题,八大常见问答题的主要内容了,今天再来为大家介绍一些其他的常见问答题,一起来了解一下吧. 常见问答题如下: 1.springmvc的控制器是不是单例模式 ...

  6. 一个字稳,云原生产品家族支撑冬奥会九大业务场景,打造云上奥运新体验

    北京冬奥会已经成为收视最高的一届冬奥会,在转播时长.技术.内容制作方式等多方面都书写了新记录.云技术的应用,是本届北京冬奥会赛事转播的一大特色. 而云原生作为云计算的新界面,如何稳定支撑北京冬奥会多个 ...

  7. springMVC请求流程详解

    SpringMVC框架是一个基于请求驱动的Web框架,并且使用了'前端控制器'模型来进行设计, 再根据'请求映射规则'分发给相应的页面控制器进行处理.核心流程: 第一步:发起请求到前端控制器(Disp ...

  8. 《微服务》九大特性重读笔记

    今天重读了Martin Fowler的<Microservices>,在此记录一下对九大特性的理解. 服务组件化 组件,是一个可以独立更换和升级的单元.就像PC中的CPU.内存.显卡.硬盘 ...

  9. 信息系统项目管理师-九大知识领域必备知识点整理

    场景 在备考阶段,九大知识领域有关的知识重点需要背诵的内容整理. 注: 博客: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相 ...

最新文章

  1. liunx 上get 不到url参数 java_thinkphp5.0 模板上直接获取url参数
  2. 图论刷水题记录(一)(最短路-----dijkstra算法)
  3. lsit集合去重复 顶级表达式
  4. [转载] 晓说——第15期:揭秘欧洲列强恩仇录
  5. OnClick,OnClientClick和OnServerClick的区别
  6. 【NOSQL 】 memcache 安装及配置分布式集群 双向复制
  7. 一步步创建 边栏 Gadget(一)
  8. CodeForces - 1437E Make It Increasing(确定首尾的最长不下降子序列)
  9. CCNA试验-1标准acl
  10. 计算机硬件基础英语ppt,计算机硬件技术基础,computer hardware technology elements,音标,读音,翻译,英文例句,英语词典...
  11. 一个JavaScript的小问题
  12. element-ui分页器的使用
  13. TFTP服务器的使用
  14. java数字转读音_java 数字转汉语读音的程序
  15. OpenCV笔记_20(1)基于dlib进行人脸识别( 图片检测 )
  16. python 文件指针详解、文件基本操作方法及在文件起始位置插入内容
  17. 精准目标群体,精确博客选择——谈feedsky经典博客Market力作
  18. python 通达信公式函数_通达信zig函数的python实现
  19. 数据结构实验6_压缩矩阵的转置、普通转置、快速转置算法
  20. 使用Keras训练Lenet网络来进行手写数字识别

热门文章

  1. spring AspectJ的Execution详解
  2. linux压缩与解压缩命令
  3. 七.Hystrix Timeout机制
  4. HDU 5908 Abelian Period 暴力
  5. poj 3077Rounders(模拟)
  6. 时光已荏苒,我还怎么让你遇见最美年华里的我
  7. Java POI 导出EXCEL经典实现 Java导出Excel
  8. JDK的动态代理深入解析(Proxy,InvocationHandler)(转)
  9. 2008秋季-计算机软件基础-0917课堂用例(2)
  10. 编程方法学18:多维数组收尾