专栏首页奕仁专栏SpringMvc源码之返回值拦截HandlerMethodReturnValueHandler

SpringMvc源码之返回值拦截HandlerMethodReturnValueHandler

与上篇类似,HandlerMethodReturnValueHandler是对返回值的解析,相关的注解有, 用法也基本一样 先看看源码,打开类RequestResponseBodyMethodProcessor,这个类是对@RequestBody和@ResponseBody进行整合的一个类,本篇只解析@ResponseBody 直接看

@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		//返回值上是否有这个注解
		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
				returnType.hasMethodAnnotation(ResponseBody.class));
	}
@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
	  //对请求体和响应体进行封装,将返回值写入响应流里,
		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

		// Try even with null return value. ResponseBodyAdvice could get involved.
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object outputValue;
		Class<?> valueType;
		Type declaredType;
	//返回值是否是String类型 进行类型转换
		if (value instanceof CharSequence) {
			outputValue = value.toString();
			valueType = String.class;
			declaredType = String.class;
		}
		else {
			outputValue = value;
			valueType = getReturnValueType(outputValue, returnType);
			declaredType = getGenericType(returnType);
		}
		//
		if (isResourceType(value, returnType)) {
			
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null) {
				Resource resource = (Resource) value;
				try {
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					outputValue = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = outputValue.getClass();
					declaredType = RESOURCE_REGION_LIST_TYPE;
				}
				catch (IllegalArgumentException ex) {
					outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
					outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
				}
			}
		}

	List<MediaType> mediaTypesToUse;
		//包装响应头 响应类型等参数
		MediaType contentType = outputMessage.getHeaders().getContentType();
		if (contentType != null && contentType.isConcrete()) {
			mediaTypesToUse = Collections.singletonList(contentType);
		}
		else {
			HttpServletRequest request = inputMessage.getServletRequest();
			List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
			List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);

			if (outputValue != null && producibleMediaTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
			......
		//将值写入response里,然后封装为对象返回到前端
		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
						//这里是进行格式转换器进行判断(xml/json等)
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (outputValue != null) {
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);
						}
				}
			}
		}
		if (outputValue != null) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}

动手实践

实现HandlerMethodReturnValueHandler

配置加载HandlerMethodReturnValueHandler顺序

package org.choviwu.movie.config;

import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.choviwu.movie.annotation.Response;
import org.choviwu.movie.util.JsonUtils;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletResponse;
import java.util.Map;

@Slf4j
public class ResponseHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {


    private final HandlerMethodReturnValueHandler handlerMethodReturnValueHandler;

    /**
     * 定义默认构造,为了让此类拦截返回值然后用@ResponseBody执行器执行包装
     * @param handlerMethodReturnValueHandler
     */
    public ResponseHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler handlerMethodReturnValueHandler) {
        this.handlerMethodReturnValueHandler = handlerMethodReturnValueHandler;
    } 

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
                returnType.hasMethodAnnotation(ResponseBody.class));

    } 
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest) throws Exception {
//        Object result = returnValue;
        Map map = Maps.newHashMap();
        map.put("code", 0);
        map.put("msg", "success");
        map.put("data", returnValue);
        mavContainer.setRequestHandled(true);
        log.info(">>>>>>>>>>>>>>>>Return Value : {}", JsonUtils.toJson(map));
		//包装好对象之后,实际执行者为responseReturnHandler
        handlerMethodReturnValueHandler.handleReturnValue(map, returnType, mavContainer, webRequest); 
    }
}

配置加载顺序

package org.choviwu.movie.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Administrator
 */
@Configuration
public class WebConfig extends WebMvcConfigurationSupport implements InitializingBean{


    @Autowired
    RequestMappingHandlerAdapter requestMappingHandlerAdapter;
 

    @Override
    public void afterPropertiesSet() throws Exception {

        List<HandlerMethodReturnValueHandler> unmodifiableList = requestMappingHandlerAdapter.getReturnValueHandlers();
        List<HandlerMethodReturnValueHandler> list = new ArrayList<>(unmodifiableList.size());
        for (HandlerMethodReturnValueHandler returnValueHandler : unmodifiableList) {
            if (returnValueHandler instanceof RequestResponseBodyMethodProcessor) {
                //将RequestResponseBodyMethodProcessor 实际返回值替换为自定义的,实际执行为RequestResponseBodyMethodProcessor
                //重要
                ResponseHandlerMethodReturnValueHandler handler = new ResponseHandlerMethodReturnValueHandler(returnValueHandler);
                list.add(handler);
            } 
            else {
                list.add(returnValueHandler);
            }
        }
        requestMappingHandlerAdapter.setReturnValueHandlers(list);
    }
}

这里就配置完毕,验证成功

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SpringMvc源码之解析参数HandlerMethodArgumentResolver

    HandlerMethodArgumentResolver是什么?它是springmvc提供的入参解析器,像平常应用的注解@RequestParam @Path...

    奕仁
  • 记一次学习SpringBoot RequestBodyAdvice ResponseBodyAdvice RestControllerAdvice【技能篇】

    今天老板给我了一套代码,然后我就拿过去研究,代码的风格是SSM + Shiro + nginx + SpringBoot的MVC架构风格,springboot,...

    奕仁
  • 项目中HandlerMethodReturnValueHandler的应用

    相信很多同学在项目开发,会遇到这种问题,就是某些字段如果为null,返回给前台,然后前端会各种null判断? 或者后端同学在返回之前对null之进行判空,然后返...

    奕仁
  • SpringBoot开发案例之整合mail队列进阶篇

    上一篇文章,我们为了解决实际场景中遇到的问题,使得其更像一个安全高效的邮件服务平台,我们引入了LinkedBlockingQueue队列对邮件发送进行流量削锋、...

    小柒2012
  • SpringBoot开发案例之整合mail队列进阶篇

    前情提要 上一篇文章,我们为了解决实际场景中遇到的问题,使得其更像一个安全高效的邮件服务平台,我们引入了LinkedBlockingQueue队列对邮件发送进行...

    小柒2012
  • SpringBoot开发案例之整合mail队列进阶篇

    上一篇文章,我们为了解决实际场景中遇到的问题,使得其更像一个安全高效的邮件服务平台,我们引入了LinkedBlockingQueue队列对邮件发送进行流量削锋、...

    小柒2012
  • @EnableDiscoveryClient和@EnableEurekaClient的区别?

    @EnableDiscoveryClient和@EnableEurekaClient的区别?在前面的服务提供者的例子中我们是用@EnableEurekaClie...

    马克java社区
  • SpringCloud gateway跨域配置

    天涯泪小武
  • Spring Controller单元测试

    SpringMVC controller测试较简单,从功能角度划分,可分为两种。一种是调用请求路径测试,另一种是直接调用Controller方法测试。 调用请求...

    YGingko
  • 10分钟搞定 SpringBoot 如何优雅读取配置文件?

    很多时候我们需要将一些常用的配置信息比如阿里云 oss 配置、发送短信的相关信息配置等等放到配置文件中。

    Guide哥

扫码关注云+社区

领取腾讯云代金券