我们在用SpringMVC做web开发的时候,有时候处理完一个请求之后会返回一个页面,有时候会返回一个字符串,有时候会返回一个json对象。通过分析源码我们知道在调用请求处理器映射方法的时候走的是同一段代码,如下:
org.springframework.web.method.support.InvocableHandlerMethod#doInvoke
protected Object doInvoke(Object... args) throws Exception { //反射设置setAccessible为true ReflectionUtils.makeAccessible(getBridgedMethod()); try { //反射调用请求处理器映射方法 return getBridgedMethod().invoke(getBean(), args); } ............ }那么SpringMVC是在哪里进行不同返回对象的映射的呢?在org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle中有这样一段代码:
try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); }这里的returnValueHandlers是HandlerMethodReturnValueHandlerComposite的实例(参考之前对HandlerMethodArgumentResolver的分析)。getReturnValueType(returnValue)获取到的是ReturnValueMethodParameter对象。这一段代码是用来处理不同的请求返回值的。所以我们不同请求的返回是由HandlerMethodReturnValueHandler的不同实现类来进行处理的。我们先看一下HandlerMethodReturnValueHandler这个接口的UML类图:
在HandlerMethodReturnValueHandler接口中有这样的两个方法:
//支持的返回值类型 boolean supportsReturnType(MethodParameter returnType); //处理返回值 void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
接下来我试着分析一下常用的几个不同返回值类型的场景。
首先我们先进入到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod这个方法中,在这个方法中有这样的一句话:
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
通过之前的分析我们知道这个returnValueHandlers是在RequestMappingHandlerAdapter的afterPropertiesSet方法中实例化的HandlerMethodReturnValueHandlerComposite。(参考SpringMVC之分析RequestMappingHandlerAdapter(二))。接着会调用org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle方法,从而调用org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue方法,我们进入到handleReturnValue这个方法中看一下:
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);//选择对应的HandlerMethodReturnValueHandler if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); }//对返回值进行处理 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); }
private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) { boolean isAsyncValue = isAsyncReturnValue(value, returnType); for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {//循环之前放入的HandlerMethodReturnValueHandler的实现类 if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) { continue; }//调用supportsReturnType方法判断是否支持对返回值的处理 if (handler.supportsReturnType(returnType)) { return handler; } } return null; }在上面的这两个方法中通过调用HandlerMethodReturnValueHandler的supportsReturnType方法来判断支持返回值类型的处理类,通过调用HandlerMethodReturnValueHandler的handleReturnValue方法来对返回值进行处理。下面我们具体的分析几个场景。
ViewNameMethodReturnValueHandler
在分析这个类之前,我们先看一个例子:
@Controller @RequestMapping("/returnValueHandler") public class ReturnValueHandlerController { @RequestMapping("/viewNameMethod") public String viewNameMethodMapping() { return "viewNameMethod"; } }我们访问一下这个请求:http://localhost:8086/returnValueHandler/viewNameMethod
这个错误很多人应该都见过吧。请求处理的结果是404。错误提示信息是:在/WEB-INF/jsp这个目录下找不到viewNameMethod.jsp这个文件。我们返回的是一个字符串,为什么这里是要去寻找viewNameMethod.jsp这个页面呢?秘密就在ViewNameMethodReturnValueHandler这个类中。在之前的分析中(SpringMVC之分析RequestMappingHandlerAdapter(二))我们知道,在RequestMappingHandlerAdapter中默认添加了一些列的HandlerMethodReturnValueHandler实现类,ViewNameMethodReturnValueHandler就在其中。我们看一下ViewNameMethodReturnValueHandler的supportsReturnType方法的内容:
@Override public boolean supportsReturnType(MethodParameter returnType) { Class<?> paramType = returnType.getParameterType(); return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType)); }如果返回值是void或者是CharSequence类型(通常是值字符串),则可以使用ViewNameMethodReturnValueHandler这个类来处理。现在我们的返回值是String类型的,ViewNameMethodReturnValueHandler支持对String返回值的处理,所以我们这里获取到的HandlerMethodReturnValueHandler的实现类为ViewNameMethodReturnValueHandler。接下来我们看一下handleReturnValue这个方法的源码:
@Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue instanceof CharSequence) {//如果是字符类型 String viewName = returnValue.toString();//获取字符串的值 mavContainer.setViewName(viewName);//设置viewName的值 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()); } }在上面的分析中我们可以看到,ViewNameMethodReturnValueHandler将我们返回的字符串映射为了视图名,所以这里就会走视图解析的流程,查找相应的视图,如果我们没有创建这个视图的话,就会报404的异常了。在上面的代码中还有一句isRedirectViewName。这个方法是判断请求是不是重定向到另外一个请求上。isRedirectViewName的源码如下:
protected boolean isRedirectViewName(String viewName) { return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:")); }如果返回的字符串是redirect:开头的,会认为这个请求会重定向到另外一个请求上面。