本系列文章的上一篇 : Spring MVC : 控制器方法处理请求的过程分析 - 5. 调用控制器方法本身
在上一篇中,我们讲到了对目标控制器方法的调用过程。目标控制器方法是由开发人员实现的,它执行完之后,会有相应的返回值,调用者会对返回值进行相应的处理,这也正是本文要讲述的内容,也就是HandlerMethodReturnValueHandler
所被设计的任务。我们再回到ServletInvocableHandlerMethod#invokeAndHandle
方法,看它拿到控制器方法返回值之后会做什么 :
// ServletInvocableHandlerMethod 代码片段
/**
* Invoke the method and handle the return value through one of the
* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
* @param webRequest the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type (not resolved)
*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 调用目标控制器方法,这一过程主要做了以下两件事情 :
// 1. 从请求分析参数值并转换成可用于目标控制器方法的结构和类型
// 2. 调用开发人员提供的目标控制器方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// 现在 returnValue 是目标控制器方法执行的返回结果,之所以使用 Object 类型接收,
// 是因为框架并不清楚开发人员会设计什么样的返回类型
// 获取控制器方法上的注解 @ResponseStatus 信息,注解 @ResponseStatus 会有两个属性 :
// status 和 reason,这里就是参考这两个属性对响应对象做处理 :
// 情况1 : status 不为 null, reason 有内容(不为null,也不为空字符串), response.setStatus(status)
// 情况2 : status 不为 null, reason 无内容(为null,或者空字符串), response.sendError(status,reason)
// 这两种情况下也会设置请求属性 : <RESPONSE_STATUS_ATTRIBUTE,status>, 重定向的话会使用该信息。
// 该过程并没有基于 returnValue 的任何动作。
// 如果 status 为 null ,可以认为这一步什么都没做
setResponseStatus(webRequest);
// 开始基于结合 @ResponseStatus 注解和返回值 returnValue 进行处理的逻辑
if (returnValue == null) {
// 控制器方法的返回值是 null
if (isRequestNotModified(webRequest)
|| getResponseStatus() != null
|| mavContainer.isRequestHandled()) {
// 针对如下三种情况组合标记请求已经被完全处理 并且 当前方法返回 :
// 1. 目标控制器方法无返回值 + 请求资源没有改变
// 2. 目标控制器方法无返回值 + 目标控制器方法注解 @ResponseStatus 属性 status 不为 null
// 3. 目标控制器方法无返回值 + mavContainer 中请求处理完成标记已经被设置
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
// 目标控制器方法有返回值 + 目标控制器方法注解 @ResponseStatus 属性 reason 有内容
// 这种情况下也将请求标记为已经处理完成,并且 当前方法返回
mavContainer.setRequestHandled(true);
return;
}
// 结合 @ResponseStatus 注解和返回值 returnValue 考虑之后,发现需要进一步处理返回值,
// 此时先将请求标记为尚未处理完成,然后使用 this.returnValueHandlers 进行处理
// 将请求处理完成标记设置为 false
mavContainer.setRequestHandled(false);
// 断言 this.returnValueHandlers 不能为空,因为接下来要处理返回值的动作需要使用到它们
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// this.returnValueHandlers 是一个 HandlerMethodReturnValueHandlerComposite 对象
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
从该方法逻辑可以看出,它在得到目标控制器方法的返回值之后,会首先结合返回值和方法上的注解属性@ResponseStatus
进行一番处理,如果发现能将请求处理标记为完成,此时便会将请求标记为完成然后结束处理,否则会使用this.returnValueHandlers
对请求结果进行进一步的处理。而this.returnValueHandlers
是一个HandlerMethodReturnValueHandlerComposite
对象,该对象组合了多个HandlerMethodReturnValueHandler
实现类对象,自身也实现了接口HandlerMethodReturnValueHandler
。HandlerMethodReturnValueHandlerComposite
对returnValue
的处理,其实是遍历自己所包含的HandlerMethodReturnValueHandler
,寻找一个可以处理该返回值的HandlerMethodReturnValueHandler
,使用它对返回值进行处理。
HandlerMethodReturnValueHandler
存在很多实现类,分别针对不同特征的返回值进行处理,这里不再做一一介绍,仅使用一些例子来看它是怎样工作的 :
ViewNameMethodReturnValueHandler
– 返回值类型为字符串,表示视图名称RequestResponseBodyMethodProcessor
– 目标控制器方法或者目标控制器类使用了注解@ResponseBody
关于
HandlerMethodReturnValueHandler
更多的信息,可以参考如下几篇文章 :
ViewNameMethodReturnValueHandler
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 如果返回值是字符串类型,将其理解为视图名称
if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
// 将视图名称设置到 mavContainer
mavContainer.setViewName(viewName);
// 如果视图名称字符串以 redirect: 开头,表明这是一个跳转指示,
// 对 mavContainer 作相应的设置
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());
}
}
RequestResponseBodyMethodProcessor
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// 将请求设置为处理完成
// 原因 : 目标控制器方法或者目标控制器类上使用了注解@responseBody,
// 所以该 HandlerMethodReturnValueHandler 会自己将返回值写入到响应完成请求的处理,
// 所以将请求设置为处理完成,告诉调用者无需后续处理该请求。不过此刻当前
// HandlerMethodReturnValueHandler 尚未真正开始处理请求,
// 下面 writeWithMessageConverters 语句执行完成,才算是请求完全被处理。
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
// 1. 协商和设置响应头部 : Accept-Ranges,Content-Disposition
// 2. 写头部信息和返回值数据体到响应对象并 flush ,这部分逻辑通过通过 Servlet 容器的响应对象实现来做,
// 比如 Tomcat 中 : 会决定 Content-Type,Content-Language,Content-Length,
// Transfer-Encoding,Date,Connection 这些头部的值,向响应输出头部,输出响应体,
// 然后调用 response 对应的 OutputStream 的 flush 方法。
// 这其中,从返回值 returnValue 到要写入到响应会涉及到类型转换和编码,着要依赖于所配置的
// 各个 HttpMessageConverter
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
上面ViewNameMethodReturnValueHandler
,RequestResponseBodyMethodProcessor
代表了两类典型的处理方式 :
requestHandled
会保持false
自身不能将请求处理完成,自身的处理逻辑会向响应对象或者
mavContainer
做设置或者传递一些信息;调用者后续需要继续处理该请求;requestHandled
会变为true
自身能将请求处理完成,直接输出和
flush
响应;调用者后续无需继续处理该请求;
通过上面的分析,我们可以总结出ServletInvocableHandlerMethod#invokeAndHandle
对控制器方法返回值处理的逻辑如下 :
- 处理控制器方法注解
@ResponseStatus
信息;@ResponseStatus
属性status
有值则设置响应状态字 :response.setStatus(status)
;@ResponseStatus
属性reason
有值则向响应对象发送错误 :response.sendError(status,reason)
;
- 以下情况组合将请求处理标记为结束,并结束该方法过程 :
- 目标控制器方法无返回值 + 请求资源没有改变 (
not-modified==true
) ; - 目标控制器方法无返回值 + 目标控制器方法注解
@ResponseStatus
属性status
有值 ; - 目标控制器方法无返回值 +
mavContainer
中请求处理完成标记已经被设置 ; - 目标控制器方法有返回值 + 方法注解
@ResponseStatus
属性reason
有内容 ;
- 目标控制器方法无返回值 + 请求资源没有改变 (
- 如果请求尚未被标记为处理完成,找一个能处理该返回值的
HandlerMethodReturnValueHandler
继续处理;- 分支 : 如果找不到合适处理该返回值的
HandlerMethodReturnValueHandler
,则会抛出异常UnsupportedOperationException
;
- 分支 : 如果找不到合适处理该返回值的
- 如果
HandlerMethodReturnValueHandler
也不能将请求处理完成,相应信息被更新到响应对象和mavContainer
,调用者继续处理该请求;通常调用者会使用
mavContainer
构造一个ModelAndView
对象继续处理:渲染和解析视图。
本系列文章链接合集 :