springMVC九大组件及一次请求流程

一、九大组件

  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());

		// 如果是重定向,放置得更多一些,比如flashMapManager
		if (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);
					//标记一下:是否是文件上传的request
					multipartRequestParsed = (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=null
					mv = 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 false
				if (!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 necessary
            mav = 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()。

@Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
		//首先执行此操作,它可能会添加 ResponseBody 建议 bean
		initControllerAdviceCache();
		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-all
    resolvers.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之后

//调用方法并通过其中一个处理返回值
	//ServletInvocableHandlerMethod
	public 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的场景为例子
@Override
	public 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.handleReturnValue
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

	@Nullable
	private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
		//先判断是不是异步返回值,遍历循环找到AsyncHandlerMethodReturnValueHandler进行判断
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
		//遍历returnValueHandlers
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
				continue;
			}
			//判断当前HandlerMethodReturnValueHandler是否能够支持当前返回值的解析
			//HandlerMethodReturnValueHandler.supportsReturnType
			//例子:如果标了@ResponseBody,那么利用RequestResponseBodyMethodProcessor进行解析
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}
  • 进入RequestResponseBodyMethodProcessor进行处理
//RequestResponseBodyMethodProcessor
	public 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());
				}
			}
		}

		// 选择使用的 MediaType
		MediaType 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);
			}
			//声明匹配上的MediaTypes
			List<MediaType> mediaTypesToUse = new ArrayList<>();
			//循环匹配,协商MediaType
			for (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/json
			selectedMediaType = 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 forward
			return;
		}
		// 执行拦截器的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 {
			//设置响应码 status
			if (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. 总结图

在这里插入图片描述

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

参考文章
参考文章

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值