欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-02-19 16:41:34
...

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;

}

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

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

 

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

 

 

 

 

 

 

 

 

 

相关标签: spring springMVC