SpringMVC——响应处理(1)【包含源码分析】

该文详细解析了SpringMVC框架中控制器方法返回值的处理流程,包括如何通过`@ResponseBody`注解触发`RequestResponseBodyMethodProcessor`作为返回结果处理器,以及内容协商过程,如何根据浏览器的Accept头和服务器端的消息转换器选择合适的数据格式进行响应。
摘要由CSDN通过智能技术生成
@Controller
public class JsonReturnController {


    @ResponseBody
    @GetMapping("/getPet")
    public Pet getPet(){

        Pet pet=new Pet();
        pet.setAge(5);
        pet.setName("lily");
        return pet;
    }
}

项目启动后 浏览器输入 http://localhost:8080/getPet 。

debug DispatcherServlet组件中其中对返回值处理的方法为
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod
中的public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object… providedArgs方法

	  try {
		  //用返回值处理器处理返回的结果
     this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}

step into 进入内部方法继续debug

	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

   //根据返回结果,返回的数据类型去选择合适的返回结果处理器
		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
   //用处理器去处理返回结果
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

解析的结果正如归纳的步骤而言

1 :获得返回结果处理器 \textcolor{red}{1:获得返回结果处理器} 1:获得返回结果处理器
在这里插入图片描述
查看RequestResponseBodyMethodProcessor的supportsReturnType方法的实现源码如下

public boolean supportsReturnType(MethodParameter returnType) {
   //所在类上有@ResponseBody或返回方法上有@ResponseBody注解,将使用RequestResponseBodyMethodProcessor作为返回结果处理器
   //getPet()方法上包含@ResponseBody注解
   return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class);
}

2 :返回结果处理器处理返回结果 \textcolor{red}{2:返回结果处理器处理返回结果} 2:返回结果处理器处理返回结果
在这里插入图片描述

写入响应结果的关键步骤:

  • 内容协商(浏览器默认以请求头的方式告知服务器端允许接受的内容类型)

  • 服务器根据自身的能力,决定服务器能生产什么样的内容类型的数据

  • SpringMVC会遍历IOC容器底层的消息转换器HttpMessageConverter 看能否处理

  • HttpMessageConverter 是一个处理消息转换的标准接口,不同的内容类型有具体的实现类去处理



public interface HttpMessageConverter<T> {
    //是否支持指定MediaType和Class的读操作【从请求获得数据】
	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    //是否支持指定MediaType和Class的写操作【向响应写入数据】
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    //获得支持的MediaType内容类型列表
	List<MediaType> getSupportedMediaTypes();
    //读,从请求获得数据
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;
    //写,向响应写入数据
	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

在这里插入图片描述


  • 使用消息转换器HttpMessageConverter向响应输出写数据
    关键代码逻辑在于writeWithMessageConverters,使用消息转换器来给返回写入返回数据,源码如下:

	@SuppressWarnings({"rawtypes", "unchecked"})
	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object body;
		Class<?> valueType;
		Type targetType;

		if (value instanceof CharSequence) {
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		else {
			body = value;
			valueType = getReturnValueType(body, returnType);
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

		if (isResourceType(value, returnType)) {
			outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
			if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
					outputMessage.getServletResponse().getStatus() == 200) {
				Resource resource = (Resource) value;
				try {
					List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
					outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
					body = HttpRange.toResourceRegions(httpRanges, resource);
					valueType = body.getClass();
					targetType = 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());
				}
			}
		}

       //内容协商
       
		MediaType selectedMediaType = null;
       //从响应中获得ContentType信息
		MediaType contentType = outputMessage.getHeaders().getContentType();
		if (contentType != null && contentType.isConcrete()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
           //响应中ContentType不为空时,以响应体中的ContentType为准【意指当前请求已被处理过,告知服务器应该响应的数据类型】
			selectedMediaType = contentType;
		}
		else {
           //获得原生的请求对象
			HttpServletRequest request = inputMessage.getServletRequest();
           //获得请求允许接收的请求类型
			List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
           //根据返回结果信息获得可以返回的处理类型
			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

           //响应结果有数据,但是没有可生成的返回结果处理类型,抛出异常,没有找到转换器去处理响应数据
			if (body != null && producibleTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}
           
           //待使用的响应结果的内容类型
			List<MediaType> mediaTypesToUse = new ArrayList<>();
           //遍历请求允许接收的类型,去check 所有的可接受的数据类型是否双方匹配
			for (MediaType requestedType : acceptableTypes) {
				for (MediaType producibleType : producibleTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
			if (mediaTypesToUse.isEmpty()) {
				if (body != null) {
					throw new HttpMediaTypeNotAcceptableException(producibleTypes);
				}
				if (logger.isDebugEnabled()) {
					logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
				}
				return;
			}

           //根据处理权重排序
			MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

           //遍历循环,获得最适合返回的处理结果类型
			for (MediaType mediaType : mediaTypesToUse) {
				if (mediaType.isConcrete()) {
					selectedMediaType = mediaType;
					break;
				}
				else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
					selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
					break;
				}
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Using '" + selectedMediaType + "', given " +
						acceptableTypes + " and supported " + producibleTypes);
			}
		}

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
           //遍历IOC容器中的消息转换器,去向Response写数据
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ?
                       //判断消息转换器是否支持Class类型与响应结果的MediaType类型间的写操作
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
                           //响应输出中写数据
							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
						}
					}
					else {
						if (logger.isDebugEnabled()) {
							logger.debug("Nothing to write: null body");
						}
					}
					return;
				}
			}
		}

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

在解析上面这段代码的时候需要,需要了解一个概念内容协商(浏览器在发送请求的时候告知服务器端将接受怎样的返回数据类型,默认通过在请求头中设定Accept头)
在这里插入图片描述
获得浏览器最适合返回得数据类型时,需要用==消息转换器==去响应Response中写数据
在这里插入图片描述
使用MappingJacksonHttpMessageConveter去写响应数据
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值