springmvc最开始支持的是返回一个web页面,主要是jsp。为了支持jsp的渲染,引入了一个ModelAndView类。所以通常情况下,早期的控制器直接返回的就是一个ModelAndView对象。
甚至在DispatcherServlet中,一个handler的返回结果就是ModelAndView类型。
但是,随着后端技术的发展,springmvc更多的是作为一种后端服务的框架,返回值已经不再局限于jsp页面,更多的是接口,注入json之类的返回值。
那么springmvc是如何处理返回值是view还是一个json呢?
在DispatcherServlet的doDispatch方法有有这么一段:
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
这个mv就是一个ModelAndView。所以默认所有的handler都是返回ModelAndView的。
再深入看下mv是如何返回的:在ServletInvocableHandlerMethod的invokeAndHandle方法中有这么一段:
try {
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
之后就会调用各种returnValueHandler来处理返回值,比方说处理json的RequestResponseBodyMethodProcessor:
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
mavContainer.setRequestHandled(true);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, webRequest);
}
这里设置了RequestHandled为true。
然后再回到RequestMappingHandlerAdapter中返回mv的一段代码:
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
如果isRequstHandled,那么就直接返回null;所以mv就是null。
再回到DispatcherServlet中doDispatch最后一段代码:
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
如果mv不为空,才进行后面的render,否则就直接返回了。
所以对于接口类的数据,mv直接是null,所以不会渲染jsp。
如果不为null又是如何渲染呢?
render最终会走到renderMergedOutputModel方法,这是一个抽象方法,有一个常用的实现类是InternalResourceView:
@Override
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 resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(request, response);
}
}
可以看到最终借助的是Servlet中的RequestDispatcher的forward方法来交给jsp处理的。jsp本质也是servlet。forward方法用于服务器内不同servlet之间进行转发处理。