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 author is declaring the controller methods as return type of String
and the return clause in the method is just returning a string such as return "/views/theview";
Can someone elaborate the internal differences of how this works?
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.
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.
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.