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;
}
这个时候会调用AbstractCachingViewResolver
的resolveViewName
方法,具体的代码如下:
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的一些扩展点的应用。