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

CSDN 2024-08-03 11:05:03 阅读 79

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

写在前面的话SpringMVC 出参处理学前准备与回顾@ResponseBody 出参处理自定义出参用法

总结陈词

CSDN.gif

写在前面的话

上一篇博文《学会 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)。

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

<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转换数据,最后是走到下方,直接输出流返回。

image.png

【总结一下,链路流程】

HandlerMethodReturnValueHandlerComposite#handleReturnValue(出参处理的入口)

HandlerMethodReturnValueHandlerComposite#selectHandler(找出参处理器)

RequestResponseBodyMethodProcessor#supportsReturnType(判断出参匹配)

RequestResponseBodyMethodProcessor#handleReturnValue(出参处理逻辑)

AbstractMessageConverterMethodProcessor#writeWithMessageConverters(找出参转换器处理)

FastJsonHttpMessageConverter#canWrite(匹配出参转换器)

RequestResponseBodyAdviceChain#beforeBodyWrite(执行write前允许修改body)

FastJsonHttpMessageConverter#write(执行实际出参转换)


自定义出参用法

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

对应的关键词:HandlerMethodReturnValueHandler、MessageConverters、 ResponseBodyAdvice

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


总结陈词

此篇文章介绍了<code>SpringMVC 出参处理相关的分析,仅供学习参考。

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

CSDN_END.gif



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。