《学会 SpringMVC 系列 · 剖析出参处理》
CSDN 2024-08-03 11:05:03 阅读 79
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍
文章目录
写在前面的话SpringMVC 出参处理学前准备与回顾@ResponseBody 出参处理自定义出参用法
总结陈词
写在前面的话
上一篇博文《学会 SpringMVC 系列 · 剖析入参处理》的学习,大致了解了<code>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)。
2、接着进入 selectHandler 方法,这个按字面意思,就是查找合适的的处理器,这里又可以看到熟悉的配方。
遍历 HandlerMethodReturnValueHandler 的列表,依次执行其 supportsReturnType 判断是否满足。
这里还有一个异步判定逻辑,暂不展开。
3、这里找到 RequestResponseBodyMethodProcessor,要求类或方法包含@ResponseBody即可。
4、找到合适的处理器后,返回执行 RequestResponseBodyMethodProcessor 的 handleReturnValue方法。
5、该方法往下走到 writeWithMessageConverters,看名字就是使用消息转换器进行write。
这里再次遍历消息转换器列表,依次执行canWrite方法判定,由于示例项目有引入了fastjson相关依赖,这边匹配到了
FastJsonHttpMessageConverter。由于这步代码比较多一些,下方贴一下源代码。
<code>// 遍历 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转换数据,最后是走到下方,直接输出流返回。
【总结一下,链路流程】
HandlerMethodReturnValueHandlerComposite#handleReturnValue(出参处理的入口)
HandlerMethodReturnValueHandlerComposite#selectHandler(找出参处理器)
RequestResponseBodyMethodProcessor#supportsReturnType(判断出参匹配)
RequestResponseBodyMethodProcessor#handleReturnValue(出参处理逻辑)
AbstractMessageConverterMethodProcessor#writeWithMessageConverters(找出参转换器处理)
FastJsonHttpMessageConverter#canWrite(匹配出参转换器)
RequestResponseBodyAdviceChain#beforeBodyWrite(执行write前允许修改body)
FastJsonHttpMessageConverter#write(执行实际出参转换)
自定义出参用法
通过上述源码分析,大概看出来,如果需要自定义出参行为,可以从三个层面下手:返回值处理器、返回值消息转换器、响应前拦截
对应的关键词:HandlerMethodReturnValueHandler、MessageConverters、 ResponseBodyAdvice
Tips:由于篇幅所限,这几个扩展点后续章节单独介绍。
总结陈词
此篇文章介绍了<code>SpringMVC 出参处理相关的分析,仅供学习参考。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。