While learning the Spring framework, I notice in the book Spring in Action, the author doesn\'t use ModelandView
method return type in controllers. The aut
Here's an in depth look.
Spring offers a DispatcherServlet
class that, typically, handles all your requests. It does this in its doDispatch(HttpServletRequest request, HttpServletResponse response)
method
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
where mv
is the final ModelAndView
object, ha
is a wrapper to your controller method annotated with @RequestMapping
.
This will usually go through a stack of method calls ending up at ServletInvocableHandlerMethod.invokeAndHandle
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle
at org.springframework.web.servlet.DispatcherServlet.doDispatch
Looking at the source
public final void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(this.responseReason)) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}
returnValue
is the object returned by your @RequestMapping
method. It goes through
this.returnValueHandlers.handleReturnValue
where Spring determines a HandlerMethodReturnValueHandler
to handle that object.
public void handleReturnValue(
Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws Exception {
HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType); // returns the appropriate handler
Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
getReturnValueHandler(returnType);
returns the appropriate handler. The HandlerMethodReturnValueHandler is an interface with a supportsReturnType
method that returns true
if the handler supports that type (String
, View
, ResponseEntity
, etc. (look for supported return types)). So the method returns the first handler it finds that supports that type and runs it.
Spring, at initialization, registers a whole slew of implementations of HandlerMethodReturnValueHandler
. Basically all the known implementing classes in its javadoc.
For example, if you return a String, Spring will use a ViewNameMethodReturnValueHandler to handle the response.
Now, which return type to use is up to you. If you wanted to return a Model
so you can use request attributes in your jsp view, you can either have Spring pass a Model
instance to your method or you can create the Model
object yourself and pass it to a ModelAndView
which your return. It's a matter of style in most cases.
In spring source code, you can see this class org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
. In the method public ModelAndView getModelAndView(...)
, you can get how sping-mvc generate ModelAandView
object.
if (returnValue instanceof HttpEntity) { // returnValue is returned Value of Handler method
handleHttpEntityResponse((HttpEntity<?>) returnValue, webRequest);
return null;
}
else if (AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) {
handleResponseBody(returnValue, webRequest);
return null;
}
else if (returnValue instanceof ModelAndView) {
ModelAndView mav = (ModelAndView) returnValue;
mav.getModelMap().mergeAttributes(implicitModel);
return mav;
}
else if (returnValue instanceof Model) {
return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());
}
else if (returnValue instanceof View) {
return new ModelAndView((View) returnValue).addAllObjects(implicitModel);
}
else if (AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class) != null) {
addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
return new ModelAndView().addAllObjects(implicitModel);
}
else if (returnValue instanceof Map) {
return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map) returnValue);
}
else if (returnValue instanceof String) { // String is here, return new ModelAndView
return new ModelAndView((String) returnValue).addAllObjects(implicitModel);
}
So in this method you can learn that spring-mvc can handle many returned types of handler method to build ModleAndView object.
Functionality wise there is no difference, both these are equivalent:
@RequestMapping(..)
public String requestMapping1(Model model){
model.addAttribute("attr1", attr1);
return "viewName";
}
@RequestMapping(..)
public ModelAndView requestMapping2(){
ModelAndView modelAndView = new ModelAndView("viewName");
modelAndView.addObject("attr1", attr1);
return modelAndView;
}
However the preferred approach is the former and that is the reason why the author has not used the latter in the book samples.