spring基本使用(25)-springMVC9-SpringMVC的返回值处理器HandlerMethodReturnValueHandler

1、什么是返回值处理器?它的作用是啥?在请求的什么阶段工作?

      答:在SpringMVC中返回值处理器用接口HandlerMethodReturnValueHandler表示。它的作用就是可以操作HandlerMethod也就是我编写的Controller中的某方法执行的返回值,至于怎么操作就是看这个返回值处理器的业务了。返回值处理器的工作时机是在Controller中方法返回后就立即执行。

2、HandlerMethodReturnValueHandler接口定义以及类图关系

public interface HandlerMethodReturnValueHandler {

	/**
	 * Whether the given {@linkplain MethodParameter method return type} is
	 * supported by this handler.
	 * @param returnType the method return type to check
	 * @return {@code true} if this handler supports the supplied return type;
	 * {@code false} otherwise
	 */
    是否支持当前的returnType返回值类型,MethodParameter这个类里面封装了被执行的Controller中本次请求的方法Method实例 + 入参类型 + 入参上的注解列表等信息,有了这些数据,返回值处理器就能判断自己需要处理什么类型的方法返回值。
	boolean supportsReturnType(MethodParameter returnType);

	/**
	 * Handle the given return value by adding attributes to the model and
	 * setting a view or setting the
	 * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
	 * to indicate the response has been handled directly.
	 * @param returnValue the value returned from the handler method
	 * @param returnType the type of the return value. This type must have
	 * previously been passed to {@link #supportsReturnType} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @throws Exception if the return value handling results in an error
	 */

    处理返回值的业务,入参是Controller方法的返回值returnValue,返回值类型描述,ModelAndViewContainer实例,还有就是请求实例。
	void handleReturnValue(Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

          类图站面比较大,就展示部分重要的返回值处理器吧:

                     

 

3、返回值处理器HandlerMethodReturnValueHandler在整个请求中的执行点:

      我们知道在RequestMappingHandlerAdapter中的invokeHandlerMethod方法里面是整个请求的核心方法,在这个方法里面会将我们的HandlerMethod实例包装成一个ServletInvocableHandlerMethod的时候,然后执行这个可执行的处理器方法,源码片段如下:

        1、将我们的HandlerMethod实例包装成一个ServletInvocableHandlerMethod实例。
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        ...		
        2、设置这个可执行处理器方法的返回值处理器列表设置为RequestMappingHandlerAdapter里面配置的返回值处理器列表。 
        invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        ...
        3、执行处理器方法。
        invocableMethod.invokeAndHandle(webRequest, mavContainer);

                   接着来到ServletInvocableHandlerMethod 的 invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs)方法:

	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
        1、先执行Controller方法,得到返回值,如果是void方法,返回值是null。
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

        2、设置请求的响应状态。
		setResponseStatus(webRequest);

        3、如果返回值是null
		if (returnValue == null) {
         3.1 如果请求是没有被修改过的 || 响应状态不为空 || mavContainer实例的requestHandled是
true这个属性很重要,它决定了是否需要进行视图解析,同时它也标记着请求已经被处理器结束,如果为true
后续的视图解析器就不会解析解析!!!!!!
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
                满足条件就设置mavContainer的请求以被处理器字段为true,表示已被处理。
				mavContainer.setRequestHandled(true);
                然后直接结束方法,这就是返回值是null的处理方式。
				return;
			}
		}
        4、如果响应状态实例的reason字段是有值的,这个是什么意思呢?比如我们在业务代码里面自行设置的响应状态信息,那么到此处的时候就不会走返回值处理器的逻辑。
		else if (StringUtils.hasText(getResponseStatusReason())) {
            满足条件就设置mavContainer的请求以被处理器字段为true,表示已被处理。
			mavContainer.setRequestHandled(true);
            然后直接结束方法。
			return;
		}

        4、如果返回值不为null 且响应状态实例里面没有reason,那就不管之前做过什么设置,先覆盖mavContainer实例的requestHandled属性为false。
		mavContainer.setRequestHandled(false);
		try {
            5、寻找支持当前返回值类型的返回值处理器,对返回值进行处理。
			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;
		}
	}

                  这个就是返回值处理器在整个请求期间的执行点。

 

4、ViewNameMethodReturnValueHandler返回值处理器原理剖析:

       这个返回值处理器支持的是方法的返回值类型是void 或者是CharSequence类型,这个类型有很多的实现,比如String就是其中一个。

       案例:

        @RequestMapping("test/save")
        public String test(@Validated(value = HumaSaveGroup.class)  Huma huma, BindingResult bindingResult) {
           huma.setAge(18);
           return "register"; //这个register是一个viewName,比如jsp的名称
        }

        我们来到ViewNameMethodReturnValueHandler的handleReturnValue(...)方法:

	@Override
	public void handleReturnValue(Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        1、判断返回值的类型是否是CharSequence类型
		if (returnValue instanceof CharSequence) {
            转成字符串表示viewName
			String viewName = returnValue.toString();

            设置mavContainer的viewName=viewName,在后续视图解析器工作的时候会使用到。
			mavContainer.setViewName(viewName);

            如果是带有"redirect:"重定向表示的viewName,那就设置mavContainer的重定向场景=true。
			if (isRedirectViewName(viewName)) {
				mavContainer.setRedirectModelScenario(true);
			}
		}
        2、如果返回值不是CharSequence类型就抛出UnsupportedOperationException异常。
		else if (returnValue != null){
			// should not happen
			throw new UnsupportedOperationException("Unexpected return type: " +
					returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
		}
	}

                  这就是ViewNameMethodReturnValueHandler的实现原理。

 

5、RequestResponseBodyMethodProcessor返回值处理器原理剖析:

      这个返回值处理器的适用场景是Controller类上标注了@ResponseBody注解 || Controller的当前执行的请求方法上标注了@ResponseBody注解注解。

      案例:

        @RequestMapping("user2")
        @ResponseBody
        private MyResponse <String> user1(@RequestBody @Validated Huma huma, BindingResult bindingResult) {
            int i = 2;
            if (i == 1) {
               throw new BusinessException(101, "BusinessException名称不能为空!");
            }
            return MyResponse.buildSuccess("register success");
        }

   我们来到RequestResponseBodyMethodProcessor的handleReturnValue(...)方法:

	@Override
	public void handleReturnValue(Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        1、先设置mavContainer的requestHandled属性=true,这样的话后续的视图解析器就不会工作了,所
以我们在写rest接口的时候不需要配置一个视图解析器也是能正常工作的。
		mavContainer.setRequestHandled(true);

        2、构建ServletServerHttpRequest请求实例
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);

        3、构建ServletServerHttpResponse响应实例
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// Try even with null return value. ResponseBodyAdvice could get involved.
        4、这里至关重要,这里会将返回值经过HttpMessageConverter将返回值经过处理,然后写入到响应体body中。例如将返回值序列化成json字符串,然后将自字符穿写入到responseBody中。
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

                   下面来分析writeWithMessageConverters(...)实现:

	protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object outputValue;
		Class<?> valueType;
		Type declaredType;

        1、如果返回值是CharSequence类型,那就转成String类型。
		if (value instanceof CharSequence) {
			outputValue = value.toString();
			valueType = String.class;
			declaredType = String.class;
		}
		else {
			outputValue = value;
			valueType = getReturnValueType(outputValue, returnType);
			declaredType = getGenericType(returnType);
		}

		HttpServletRequest request = inputMessage.getServletRequest();

        2、获取请求的媒体类型列表,在请求是不设置的话将会返回返回一个"*/*"表示通配。
		List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);

        3、获取将产生的媒体类型列表,如application/json等,默认会添加很多类型。这里的意思就是响应体你的内容类型是啥,这个是需要确定的。
		List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

        4、如果返回值不为空且将产生的媒体类型是空,那就抛出异常。
		if (outputValue != null && producibleMediaTypes.isEmpty()) {
			throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
		}

        5、分析到可用的媒体类型。
		Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
		for (MediaType requestedType : requestedMediaTypes) {
			for (MediaType producibleType : producibleMediaTypes) {
				if (requestedType.isCompatibleWith(producibleType)) {
					compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
				}
			}
		}
        6、如果分析下来没有可用的媒体类型就抛出HttpMediaTypeNotAcceptableException异常,这个错误是比较常见的。
		if (compatibleMediaTypes.isEmpty()) {
			if (outputValue != null) {
				throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
			}
			return;
		}

		List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
        7、按权重将媒体类型进行排序,也就是说我们可以设置多个媒体类型,并给它们都分配权重,在此处会使用其权重正排序。
		MediaType.sortBySpecificityAndQuality(mediaTypes);

        8、选择一个媒体类型。
		MediaType selectedMediaType = null;
		for (MediaType mediaType : mediaTypes) {
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
				break;
			}
		}

        9、如果选到了一个可用的媒体类型,那就按此媒体类型进行响应体的输出。
		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();

            10、选者合适的HttpMessageConvert进行响应体数据输出。
			for (HttpMessageConverter<?> messageConverter : this.messageConverters) {

                11、如果是常用的HttpMessageConverter。
				if (messageConverter instanceof GenericHttpMessageConverter) {

                    12、那就判断其是否能够将返回值按照选择的媒体类型进行响应输出。
					if (((GenericHttpMessageConverter) messageConverter).canWrite(
							declaredType, valueType, selectedMediaType)) {
                       
                        13、如果可以那就先调用所有ResponseBodyAdvice类型的beforeBodyWrite方法,这里跟在入参解析的时候,读requestBody之前的通知是一个道理。
						outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
								(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
								inputMessage, outputMessage);

                        14、如果需要输出的值不为空(输出的值也就是返回值,一般情况下。)
						if (outputValue != null) {
							addContentDispositionHeader(inputMessage, outputMessage);

                            15、使用当前的GenericHttpMessageConverteri将返回值按照媒体类型写入到响应体中!!!!!!!!此处很重要。
							((GenericHttpMessageConverter) messageConverter).write(
									outputValue, declaredType, selectedMediaType, outputMessage);
							if (logger.isDebugEnabled()) {
								logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
										"\" using [" + messageConverter + "]");
							}
						}
                        16、写完响应体立即结束当前函数。
						return;
					}
				}
				else if (messageConverter.canWrite(valueType, selectedMediaType)) {
					outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
							inputMessage, outputMessage);
					if (outputValue != null) {
						addContentDispositionHeader(inputMessage, outputMessage);
						((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
						if (logger.isDebugEnabled()) {
							logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
									"\" using [" + messageConverter + "]");
						}
					}
					return;
				}
			}
		}

		if (outputValue != null) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}

                   这就是RequestResponseBodyMethodProcessor返回值处理器的原理。

 

6、下一节我们分析SpringMVC的视图解析器ViewResolver

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值