Spring MVC : 控制器方法处理请求的过程分析 - 6. 控制器方法返回值处理

本文深入探讨SpringMVC框架中控制器方法返回值的处理流程,包括如何结合@ResponseStatus注解处理响应状态,以及如何利用HandlerMethodReturnValueHandler进行具体返回值的解析与响应构建。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本系列文章的上一篇 : 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实现类对象,自身也实现了接口HandlerMethodReturnValueHandlerHandlerMethodReturnValueHandlerCompositereturnValue的处理,其实是遍历自己所包含的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代表了两类典型的处理方式 :

  1. requestHandled 会保持 false

    自身不能将请求处理完成,自身的处理逻辑会向响应对象或者mavContainer做设置或者传递一些信息;调用者后续需要继续处理该请求;

  2. requestHandled 会变为 true

    自身能将请求处理完成,直接输出和flush响应;调用者后续无需继续处理该请求;

通过上面的分析,我们可以总结出ServletInvocableHandlerMethod#invokeAndHandle对控制器方法返回值处理的逻辑如下 :

  1. 处理控制器方法注解@ResponseStatus信息;
    1. @ResponseStatus 属性 status 有值则设置响应状态字 : response.setStatus(status);
    2. @ResponseStatus 属性 reason 有值则向响应对象发送错误 :response.sendError(status,reason);
  2. 以下情况组合将请求处理标记为结束,并结束该方法过程 :
    1. 目标控制器方法无返回值 + 请求资源没有改变 (not-modified==true) ;
    2. 目标控制器方法无返回值 + 目标控制器方法注解 @ResponseStatus 属性 status 有值 ;
    3. 目标控制器方法无返回值 + mavContainer 中请求处理完成标记已经被设置 ;
    4. 目标控制器方法有返回值 + 方法注解 @ResponseStatus 属性 reason 有内容 ;
  3. 如果请求尚未被标记为处理完成,找一个能处理该返回值的HandlerMethodReturnValueHandler 继续处理;
    1. 分支 : 如果找不到合适处理该返回值的HandlerMethodReturnValueHandler,则会抛出异常UnsupportedOperationException;
  4. 如果HandlerMethodReturnValueHandler 也不能将请求处理完成,相应信息被更新到响应对象和mavContainer,调用者继续处理该请求;

    通常调用者会使用mavContainer构造一个ModelAndView对象继续处理:渲染和解析视图。

本系列文章链接合集 :

  1. 概述
  2. 执行信息记录对象ModelAndViewContainer的准备
  3. 请求参数的获取
  4. 控制器方法参数值绑定 HandlerMethodArgumentResolver
  5. 控制器方法参数值的验证 MethodValidationInterceptor
  6. 调用控制器方法本身
  7. 控制器方法返回值处理
  8. 包装返回结果 : 从ModelAndViewContainer对象构造ModelAndView对象

参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值