Controller handler method supported return types

2020-04-21 04:51发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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.