SpringMVC——响应处理(1)【包含源码分析】
创始人
2024-05-27 05:46:12
0
@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

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

在这里插入图片描述


  • 使用消息转换器HttpMessageConverter向响应输出写数据
    关键代码逻辑在于writeWithMessageConverters,使用消息转换器来给返回写入返回数据,源码如下:
@SuppressWarnings({"rawtypes", "unchecked"})protected  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 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 acceptableTypes = getAcceptableMediaTypes(request);//根据返回结果信息获得可以返回的处理类型List producibleTypes = getProducibleMediaTypes(request, valueType, targetType);//响应结果有数据,但是没有可生成的返回结果处理类型,抛出异常,没有找到转换器去处理响应数据if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}//待使用的响应结果的内容类型List 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>) 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去写响应数据
在这里插入图片描述

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...