SpringMVC源码系列(六)@ResponseBody解析和视图渲染的过程

1.写在前面

上篇博客大致的介绍了SpringMVC中调用对应的过程,同时也返回值的处理机制简单的讲了下,没有具体的讲的@ResponseBody注解的处理,以及视图渲染的过程,这些问题的细节都没有讲清楚,笔者打算在这篇博客中讲清楚。

2.@ResponseBody解析的过程

笔者先带着大家看下SpringMVC中处理的返回值调用的代码,具体的如下

@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());
	}
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

这块的调用过程,以及是怎么调用到的,笔者已经讲的很清楚了,读者不清楚的可以看下笔者前面的博客。我们再来看下测试的代码,具体的内容如下:

package com.ys.config;

import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.*;

import java.util.List;

@Configuration
@ComponentScan("com.ys.controller")
public class AppConfig extends WebMvcConfigurationSupport {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/page/", ".html");
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter httpMessageConverter = new FastJsonHttpMessageConverter();
        converters.add(httpMessageConverter);
    }

}

package com.ys.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/model.do")
    @ResponseBody
    public Map<String,String> model() {
        Map<String, String> map = new HashMap<>();
        map.put("name", "king");
        map.put("age", "18");
        return map;
    }
}

笔者先带大家看下是那个返回参数的处理器处理这个@ResponseBody注解的,我们直接在对应的方法上打上断点,然后在浏览器上访问对应地方,然后断点调试,具体的如下:

在这里插入图片描述

可以发现与之匹配的返回值的处理器是RequestResponseBodyMethodProcessor,于是我们打开这个类RequestResponseBodyMethodProcessor,先看下这个类的supportsReturnType具体的代码如下:

public boolean supportsReturnType(MethodParameter returnType) {
	return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
			returnType.hasMethodAnnotation(ResponseBody.class));
}

可以发现这个类上有@ResponseBody注解或者这个方法有@ResponseBody注解即可匹配成功,然后我们需要看下这个类中处理@ResponseBody注解的核心方法handleReturnValue,具体代码如下:

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
		throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    //视图是否需要解析,如果是true就不需要解析
	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);
}

上面的代码主要的核心就是调用writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);方法,写出对应的消息,具体的代码如下:

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
		ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
		throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

	Object body;
	Class<?> valueType;
	Type targetType;

    //判断传进来的value的值是不是CharSequence类型
	if (value instanceof CharSequence) {
        //转成string
		body = value.toString();
		valueType = String.class;
		targetType = String.class;
	}
	else {//其他的类型,需要我们处理
		body = value;
        //获取返回值的类型并设置上去
		valueType = getReturnValueType(body, returnType);
        //获取目标的类型,包括泛型
		targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
	}

    //判断是否是Resource类型,明显这儿不是
	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 selectedMediaType = null;
    //获取媒体类型 这儿取出来的是null
	MediaType contentType = outputMessage.getHeaders().getContentType();
    //这儿也是false,因为我们没有媒体类型
	boolean isContentTypePreset = contentType != null && contentType.isConcrete();
	if (isContentTypePreset) {
		if (logger.isDebugEnabled()) {
			logger.debug("Found 'Content-Type:" + contentType + "' in response");
		}
		selectedMediaType = contentType;
	}
	else {
        //获取对应的request
		HttpServletRequest request = inputMessage.getServletRequest();
        //获取允许的类型
		List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
        //获取可生产的类型
		List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

		if (body != null && producibleTypes.isEmpty()) {
			throw new HttpMessageNotWritableException(
					"No converter found for return value of type: " + valueType);
		}
        //要使用的媒体类型
		List<MediaType> mediaTypesToUse = new ArrayList<>();
        //遍历找到要使用的媒体类型
		for (MediaType requestedType : acceptableTypes) {
			for (MediaType producibleType : producibleTypes) {
				if (requestedType.isCompatibleWith(producibleType)) {
					mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
				}
			}
		}
		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) {
		selectedMediaType = selectedMediaType.removeQualityValue();
        //遍历消息转换器,这儿这个消息转换器,是我们人为添加的,至于什么时候添加的,笔者下面会讲到
		for (HttpMessageConverter<?> converter : this.messageConverters) {
			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);
				if (body != null) {
					Object theBody = body;
					LogFormatUtils.traceDebug(logger, traceOn ->
							"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
                    //添加内容处置标题
					addContentDispositionHeader(inputMessage, outputMessage);
					if (genericConverter != null) {
                        //开始写出去
						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;
			}
		}
	}

    //抛出异常
	if (body != null) {
		Set<MediaType> producibleMediaTypes =
				(Set<MediaType>) inputMessage.getServletRequest()
						.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

		if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
			throw new HttpMessageNotWritableException(
					"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
		}
		throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
	}
}

上面的处理逻辑就是先处理对应的类型,然后调用对应的消息转化器中的write方法,将内容写出去。那么这儿的消息转换器什么时候添加的?由于我们的配置类是继承了WebMvcConfigurationSupport,所以这个类也是会被解析的,而其中有一个方法routerFunctionMapping具体的内容如下,初始化RouterFunctionMapping的时候调用如下的方法。

@Bean
public RouterFunctionMapping routerFunctionMapping(
		@Qualifier("mvcConversionService") FormattingConversionService conversionService,
		@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

	RouterFunctionMapping mapping = new RouterFunctionMapping();
	mapping.setOrder(3);
	mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
	mapping.setCorsConfigurations(getCorsConfigurations());
    //设置消息转换器
	mapping.setMessageConverters(getMessageConverters());
	return mapping;
}

上面的代码会调用mapping.setMessageConverters(getMessageConverters());来设置消息转换器,具体的代码如下:

protected final List<HttpMessageConverter<?>> getMessageConverters() {
    //走来默认是空的
	if (this.messageConverters == null) {
		this.messageConverters = new ArrayList<>();
        //调用我们写好的添加的方法
		configureMessageConverters(this.messageConverters);
        //如果我们没有添加,就使用默认的
		if (this.messageConverters.isEmpty()) {
			addDefaultHttpMessageConverters(this.messageConverters);
		}
        //再调用我们写好的剔除的方法。
		extendMessageConverters(this.messageConverters);
	}
	return this.messageConverters;
}

至此整个@ResponseBody注解的处理流程就讲完了

3.视图的渲染过程

视图的渲染过程又分为两种,一种是返回String类型的视图的渲染,一种是返回ModelAndView的视图的渲染。

3.1String类型的视图渲染

我们还是和上面的一样的操作手段,先来看下是那个返回值的参数处理器处理这种类型的,具体的如下图:

在这里插入图片描述

可以发现匹配的是ViewNameMethodReturnValueHandler类,笔者先带读者看下这个类的supportsReturnType的方法

@Override
public boolean supportsReturnType(MethodParameter returnType) {
	Class<?> paramType = returnType.getParameterType();
   	//返回值不是void,同时这个返回值属于这个类型CharSequence
	return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}

返回值是属于这个类型CharSequence,其中String是实现了这个类,所以满足,所以我们继续看这个类的handleReturnValue方法,具体的代码如下:

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    //返回值是属于CharSequence类型的
	if (returnValue instanceof CharSequence) {
        //获取返回值的名称
		String viewName = returnValue.toString();
        //将视图的容器的视图名称设置进去
		mavContainer.setViewName(viewName);
        //判断是否是重定向,主要是这个返回前面有没有加redirect:
		if (isRedirectViewName(viewName)) {
            //设置对应的重定向的标识
			mavContainer.setRedirectModelScenario(true);
		}
	}
	else if (returnValue != null) {
		// should not happen
		throw new UnsupportedOperationException("Unexpected return type: " +
				returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
	}
}

上面就给指定的视图容器设置对应的视图名称,同时判断这个视图是否是重定向(主要是判断返回值前面有没有加redirect:)若是是重定向,就设置对应的重定向的标识,最后返回出去,要执行getModelAndView(mavContainer, modelFactory, webRequest);方法,具体的代码如下:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
		ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

    //将model中设置的值存到request域中
	modelFactory.updateModel(webRequest, mavContainer);
    //判断是否需要视图解析,如果是true就不需要解析
	if (mavContainer.isRequestHandled()) {
		return null;
	}
    //获取model
	ModelMap model = mavContainer.getModel();
    //创建一个新的视图对象
	ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    //判断视图的名称是不是String,如果不是重新设置一下
	if (!mavContainer.isViewReference()) {
		mav.setView((View) mavContainer.getView());
	}
    //判断model是不是RedirectAttributes
	if (model instanceof RedirectAttributes) {
		Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
		HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
		if (request != null) {
			RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
		}
	}
	return mav;
}

上面的代码就是进行了一些判断,同时创建的了一个视图对象,然后直接返回,记住这儿的视图还不是真正的视图,笔者带着大家继续看剩下的代码,具体的如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// Actually invoke the handler.
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

	if (asyncManager.isConcurrentHandlingStarted()) {
		return;
	}

    //采用默认的视图
	applyDefaultViewName(processedRequest, mv);
    //调用拦截器的方法
	mappedHandler.applyPostHandle(processedRequest, response, mv);
	//处理视图		
	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		
	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);
		}
	}
}

上面的代码真正的处理视图的是在processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);具体的内容如下:

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 {
            //再次获取handler
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			mv = processHandlerException(request, response, handler, exception);
			errorView = (mv != null);
		}
	}

	// Did the handler return a view to 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.");
		}
	}

	if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
		// Concurrent handling started during a forward
		return;
	}

	if (mappedHandler != null) {
		// Exception (if any) is already handled..
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
}

上面的代码,走来判断有没有异常的,如果有异常,直接走异常的视图,如果没有异常,直接调用render(mv, request, response);来渲染视图,具体的代码如下:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	// Determine locale for request and apply it to the response.
    // 确定请求的语言环境并将其应用于响应。
	Locale locale =
			(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
	response.setLocale(locale);

	View view;
    //获取视图的名称
	String viewName = mv.getViewName();
	if (viewName != null) {
		// We need to resolve the view name.
        // 解析视图名称
		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.
        // 这种无需查找:ModelAndView对象包含实际的View对象
		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 {
		if (mv.getStatus() != null) {
			response.setStatus(mv.getStatus().value());
		}
		view.render(mv.getModelInternal(), request, response);
	}
	catch (Exception ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Error rendering view [" + view + "]", ex);
		}
		throw ex;
	}
}

上面会调用resolveViewName(viewName, mv.getModelInternal(), locale, request);方法解析视图,具体的代码如下:

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
		Locale locale, HttpServletRequest request) throws Exception {

	if (this.viewResolvers != null) {
        //这儿会有一个默认的视图解析器,至于怎么添加的,可以参考笔者前面的博客
        //这儿默认的视图解析器是ViewResolverComposite
		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
	}
	return null;
}

于是上面会调用默认的视图解析器,来解析这个视图,主要调用的resolveViewName(viewName, locale);方法,具体的代码如下:

public View resolveViewName(String viewName, Locale locale) throws Exception {
	for (ViewResolver viewResolver : this.viewResolvers) {
		View view = viewResolver.resolveViewName(viewName, locale);
		if (view != null) {
			return view;
		}
	}
	return null;
}

这个时候会调用AbstractCachingViewResolverresolveViewName方法,具体的代码如下:

public View resolveViewName(String viewName, Locale locale) throws Exception {
    //判断是否有缓存,没有缓存创建一个
	if (!isCache()) {
		return createView(viewName, locale);
	}
	else {//有缓存直接从缓存中取
		Object cacheKey = getCacheKey(viewName, locale);
		View view = this.viewAccessCache.get(cacheKey);
		if (view == null) {
			synchronized (this.viewCreationCache) {
				view = this.viewCreationCache.get(cacheKey);
				if (view == null) {
					// Ask the subclass to create the View object.
					view = createView(viewName, locale);
					if (view == null && this.cacheUnresolved) {
						view = UNRESOLVED_VIEW;
					}
					if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
						this.viewAccessCache.put(cacheKey, view);
						this.viewCreationCache.put(cacheKey, view);
					}
				}
			}
		}
		else {
			if (logger.isTraceEnabled()) {
				logger.trace(formatKey(cacheKey) + "served from cache");
			}
		}
		return (view != UNRESOLVED_VIEW ? view : null);
	}
}

上面会调用CreateView方法来创建视图,具体的代码如下:

protected View createView(String viewName, Locale locale) throws Exception {
	// If this resolver is not supposed to handle the given view,
	// return null to pass on to the next resolver in the chain.
	if (!canHandle(viewName, locale)) {
		return null;
	}

	// Check for special "redirect:" prefix.
    // 重定向处理的逻辑
	if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
        //重定向的Url截取出来
		String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
		RedirectView view = new RedirectView(redirectUrl,
				isRedirectContextRelative(), isRedirectHttp10Compatible());
		String[] hosts = getRedirectHosts();
		if (hosts != null) {
			view.setHosts(hosts);
		}
		return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
	}

	// Check for special "forward:" prefix.
    // 转发处理的逻辑
	if (viewName.startsWith(FORWARD_URL_PREFIX)) {
		String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
		InternalResourceView view = new InternalResourceView(forwardUrl);
		return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
	}

	// Else fall back to superclass implementation: calling loadView.
    // 如果都不是调用父类的createView方法
	return super.createView(viewName, locale);
}

需要注意的是如果是重定向返回的是RedirectView,如果是转发返回的是InternalResourceView,最后如果既不是转发也不是重定向就会调用父类的createView方法,具体的代码如下:

protected View createView(String viewName, Locale locale) throws Exception {
	return loadView(viewName, locale);
}
protected View loadView(String viewName, Locale locale) throws Exception {
	AbstractUrlBasedView view = buildView(viewName);
	View result = applyLifecycleMethods(viewName, view);
	return (view.checkResource(locale) ? result : null);
}

这个时候可以得出结论返回的是AbstractUrlBasedView,然后返回对应View的对象,这儿有一个点需要注意下,就是调用buildView(viewName);的时候,会将前缀和后缀拼接上去,具体的如下:

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
	Class<?> viewClass = getViewClass();
	Assert.state(viewClass != null, "No view class");

	AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
    //拼接视图
	view.setUrl(getPrefix() + viewName + getSuffix());
    //设置属性值
	view.setAttributesMap(getAttributesMap());

	String contentType = getContentType();
	if (contentType != null) {
		view.setContentType(contentType);
	}

	String requestContextAttribute = getRequestContextAttribute();
	if (requestContextAttribute != null) {
		view.setRequestContextAttribute(requestContextAttribute);
	}

	Boolean exposePathVariables = getExposePathVariables();
	if (exposePathVariables != null) {
		view.setExposePathVariables(exposePathVariables);
	}
	Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
	if (exposeContextBeansAsAttributes != null) {
		view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
	}
	String[] exposedContextBeanNames = getExposedContextBeanNames();
	if (exposedContextBeanNames != null) {
		view.setExposedContextBeanNames(exposedContextBeanNames);
	}

	return view;
}

最终返回到render方法,代码忘记的,可以看下上面的代码,最后会调用view.render(mv.getModelInternal(), request, response);开始真正的渲染视图了,具体的代码如下:

public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
		HttpServletResponse response) throws Exception {

	if (logger.isDebugEnabled()) {
		logger.debug("View " + formatViewName() +
				", model " + (model != null ? model : Collections.emptyMap()) +
				(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
	}

    //合并输出的视图模型
	Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
	prepareResponse(request, response);
    //完成跳转
	renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
protected void renderMergedOutputModel(
		Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

	// Expose the model object as request attributes.
	exposeModelAsRequestAttributes(model, request);

	// Expose helpers as request attributes, if any.
	exposeHelpers(request);

	// Determine the path for the request dispatcher.
	String dispatcherPath = prepareForRendering(request, response);

	// Obtain a RequestDispatcher for the target resource (typically a JSP).
	RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
	if (rd == null) {
		throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
				"]: Check that the corresponding file exists within your web application archive!");
	}

	// If already included or response already committed, perform include, else forward.
	if (useInclude(request, response)) {
		response.setContentType(getContentType());
		if (logger.isDebugEnabled()) {
			logger.debug("Including [" + getUrl() + "]");
		}
		rd.include(request, response);
	}

	else {
		// Note: The forwarded resource is supposed to determine the content type itself.
		if (logger.isDebugEnabled()) {
			logger.debug("Forwarding to [" + getUrl() + "]");
		}
        //跳转
		rd.forward(request, response);
	}
}

终于看到我们的核心的代码了,跳转页面了rd.forward(request, response);,由于调用的是view.render(mv.getModelInternal(), request, response);方法,所以每一种的情况的方法都是不同的,我们先来看下重定向的,重定向的类是RedirectView,具体的代码如下:

protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
		HttpServletResponse response) throws IOException {

	String targetUrl = createTargetUrl(model, request);
	targetUrl = updateTargetUrl(targetUrl, model, request, response);

	// Save flash attributes
	RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);

	// Redirect 关键的代码,重定向
	sendRedirect(request, response, targetUrl, this.http10Compatible);
}

同时也看到我们关键的代码,重定向的代码,再来看下我们转发的情况,转发的类是InternalResourceView,和上面既不是转发也不是重定向的是调用的一个方法。至此整个视图的渲染的过程就讲完了。

3.2ModelAndView视图渲染

前面笔者已经讲完了String类型返回值的视图渲染了,在本节笔者要讲的就是ModelAndView的视图渲染了。老规矩先看下是那个返回的处理器处理这种类型的返回值,具体的如下:

在这里插入图片描述

最终发现是ModelAndViewMethodReturnValueHandler这个类来处理这种类型的返回值,于是我们点开这个类看这个类的handleReturnValue方法,具体的内容如下:

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    //如果返回值为空的,直接结束当前的方法,同时也设置了这个视图不需要解析
	if (returnValue == null) {
		mavContainer.setRequestHandled(true);
		return;
	}

	ModelAndView mav = (ModelAndView) returnValue;
    //判断view的类型是不是String
	if (mav.isReference()) {
        //获取视图的名称
		String viewName = mav.getViewName();
        //设置视图名称
		mavContainer.setViewName(viewName);
        //设置是否是重定向
		if (viewName != null && isRedirectViewName(viewName)) {
			mavContainer.setRedirectModelScenario(true);
		}
	}
	else {
        //如果view的类型不是String
		View view = mav.getView();
        //就设置到View中去
		mavContainer.setView(view);
        //设置是否是重定向
		if (view instanceof SmartView && ((SmartView) view).isRedirectView()) {
			mavContainer.setRedirectModelScenario(true);
		}
	}
	mavContainer.setStatus(mav.getStatus());
    //设置你设置的属性
	mavContainer.addAllAttributes(mav.getModel());
}

上面的代码,就是判断一下视图的类型,然后根据对应的视图类型来设置模型和视图容器,然后同时将我们方法中设置的属性也设置到模型和视图的容器中。然后就需要看getModelAndView方法,具体的代码如下:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

    //将我们前面设置的属性设置到request域中
	modelFactory.updateModel(webRequest, mavContainer);
	if (mavContainer.isRequestHandled()) {
		return null;
	}
    //获取我们设置的属性
	ModelMap model = mavContainer.getModel();
    //创建新的模型视图容器对象
	ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
	if (!mavContainer.isViewReference()) {
		mav.setView((View) mavContainer.getView());
	}
	if (model instanceof RedirectAttributes) {
		Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
		HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
		if (request != null) {
			RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
		}
	}
    //返回
	return mav;
}

上面的代码和前面是一样的,执行的内容大致也是一样的。然后由于是新创建的ModelAndView对象,后面的逻辑就是一样,笔者这儿就不赘述了。

4.写在最后

由于篇幅的原因,笔者这篇博客就讲了@ResponseBody解析的过程以及视图渲染的过程,其实SpringMVC提供了很多的返回值的处理器,执行的逻辑都是一样的,笔者在这就不赘述,笔者下篇博客打算把SpringMVC总结一下,同时讲讲SpringMVC的一些扩展点的应用。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值