《学会 SpringMVC 系列 · 剖析出参处理》

avatar
作者
筋斗云
阅读量:0

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

CSDN.gif

写在前面的话

上一篇博文《学会 SpringMVC 系列 · 剖析入参处理》的学习,大致了解了SpringMVC请求流程中的入参处理环节,接下来介绍出参处理相关分析。后续的几篇博文,会将流程中涉及的若干关键环节单独拿出来讲解,并结合实战中的运用,帮助领略SpringMVC带来的定制和扩展能力。

相关博文
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》


SpringMVC 出参处理

学前准备与回顾

与入参处理一致,本篇 SpringMVC 源码分析系列文章,继续使用 《搭建拥有数据交互的 SpringBoot 》博文搭建的 SpringBoot3.x 项目为基础,以此学习相关源码,对应 SpringMVC 版本为 6.1.11。另外,为保证知识连贯性,继续总结和回顾一下主体流程。

【一次请求的主链路节点】
DispatcherServlet#doDispatch(入口方法)
DispatcherServlet#getHandler(根据path找到对应的HandlerExecutionChain
DispatcherServlet#getHandlerAdapter(根据handle找到对应的HandlerAdapter
HandlerExecutionChain#applyPreHandle(触发拦截器的前置逻辑)
AbstractHandlerMethodAdapter#handle(核心逻辑,下方展开)
HandlerExecutionChain#applyPostHandle(触发拦截器的后置逻辑)

【核心handle方法的主链路节点】
RequestMappingHandlerAdapter#handleInternal(入口方法)
RequestMappingHandlerAdapter#invokeHandlerMethod(入口方法2)
ServletInvocableHandlerMethod#invokeAndHandle(入口方法3)
InvocableHandlerMethod#invokeForRequest(参数和实际执行的所在,3.1)
InvocableHandlerMethod#getMethodArgumentValues(参数处理,3.1.1)
InvocableHandlerMethod#doInvoke(实际执行,3.1.2)
HandlerMethodReturnValueHandlerComposite#handleReturnValue(返回处理,3.2)

Tips:基于上方内容,本篇出参处理将继续从HandlerMethodReturnValueHandlerComposite展开。


@ResponseBody 出参处理

由于目前大部分场景都是前后端分离开发模式,后端较少返回视图页面,基本都采用 @ResponseBody 返回数据。
这里也以最常见的@ResponseBody为示例,展开介绍。

@ResponseBody @RequestMapping("/studyJson") public ZyTeacherInfo studyJson(@RequestBody ZyTeacherInfo teacherInfo) {     teacherInfo.setTeaName("战神");     return teacherInfo; } 

【运行流程】
1、先跑一下接口,忽略之前的逻辑,断点在出参处理的入口处,如下所示。
这里 returnValue 是前面接口实际返回的数据,returnType 是返回值类型(MethodParameter)。
image.png
2、接着进入 selectHandler 方法,这个按字面意思,就是查找合适的的处理器,这里又可以看到熟悉的配方。
遍历 HandlerMethodReturnValueHandler 的列表,依次执行其 supportsReturnType 判断是否满足。
这里还有一个异步判定逻辑,暂不展开。
image.png
3、这里找到 RequestResponseBodyMethodProcessor,要求类或方法包含@ResponseBody即可。
image.png
4、找到合适的处理器后,返回执行 RequestResponseBodyMethodProcessor 的 handleReturnValue方法。
image.png
5、该方法往下走到 writeWithMessageConverters,看名字就是使用消息转换器进行write。
这里再次遍历消息转换器列表,依次执行canWrite方法判定,由于示例项目有引入了fastjson相关依赖,这边匹配到了
FastJsonHttpMessageConverter。由于这步代码比较多一些,下方贴一下源代码。
image.png

// 遍历 HttpMessageConverter 出参转换器列表 for (HttpMessageConverter<?> converter : this.messageConverters) { 	GenericHttpMessageConverter genericConverter = 			(converter instanceof GenericHttpMessageConverter ghmc ? ghmc : null);      // 匹配符合要求的出参转换器,这里找到了FastJsonHttpMessageConverter 	if (genericConverter != null ? 			((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : 			converter.canWrite(valueType, selectedMediaType)) {          // 再正式write之前,还会先调用ResponseBodyAdvice列表的beforeBodyWrite方法 		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; 	} } 

6、这里在执行 FastJsonHttpMessageConverter#write 之前,会先执行 RequestResponseBodyAdviceChain 的beforeBodyWrite方法,遍历 ResponseBodyAdvice 列表,依次执行 supports 方法进行匹配。
注意,如果有自定义的ResponseBodyAdvice此时就可以触发,这个也是一个扩展点。

private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType, 		Class<? extends HttpMessageConverter<?>> converterType, 		ServerHttpRequest request, ServerHttpResponse response) { 	for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) { 		if (advice.supports(returnType, converterType)) { 			body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType, 					contentType, converterType, request, response); 		} 	} 	return body; } 

7、处理完上述步骤后,正式使用FastJsonHttpMessageConverter进行write转换数据,最后是走到下方,直接输出流返回。
image.png

【总结一下,链路流程】
HandlerMethodReturnValueHandlerComposite#handleReturnValue(出参处理的入口)
HandlerMethodReturnValueHandlerComposite#selectHandler(找出参处理器)
RequestResponseBodyMethodProcessor#supportsReturnType(判断出参匹配)
RequestResponseBodyMethodProcessor#handleReturnValue(出参处理逻辑)
AbstractMessageConverterMethodProcessor#writeWithMessageConverters(找出参转换器处理)
FastJsonHttpMessageConverter#canWrite(匹配出参转换器)
RequestResponseBodyAdviceChain#beforeBodyWrite(执行write前允许修改body)
FastJsonHttpMessageConverter#write(执行实际出参转换)


自定义出参用法

通过上述源码分析,大概看出来,如果需要自定义出参行为,可以从三个层面下手:返回值处理器、返回值消息转换器、响应前拦截
对应的关键词:HandlerMethodReturnValueHandler、MessageConverters、 ResponseBodyAdvice

Tips:由于篇幅所限,这几个扩展点后续章节单独介绍。


总结陈词

此篇文章介绍了SpringMVC 出参处理相关的分析,仅供学习参考。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

CSDN_END.gif

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!