本系列文章皆在分析SpringMVC
的核心组件和工作原理,让你从SpringMVC
浩如烟海的代码中跳出来,以一种全局的视角来重新审视SpringMVC
的工作原理.
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
前言
相信对于使用SpringMVC
的开发者而言,对于注解 @RequestBody
和 @ResponseBody
两个注解肯定不会陌生。当在控制层
使用该注解后,SpringMVC
在处理返回内容时,便会分别完成请求报文到 Java 对象 和Java 对象到响应报文的转换。
而这背后的转换的逻辑便是依赖本文所介绍的 HttpMessageConverter
消息转换机制来实现的。
配置HttpMessageConverter
在开始分析HttpMessageConverter
之前,我们先来看一段使用与HttpMessageConverter
相关的配置信息。
xml
<!-- 返回值配置json-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<!-- 配置HttpMessageConverter相关信息--->
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!--协定返回内容格式信息 -->
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
相信熟悉SpringMVC
的开发者一定会非常熟悉上述代码的含义,上述xml
配置文件的逻辑为SpringMVC
内部指定一个HttpMeesageConverter
的实现类,并协定请求的编码格式和返回内容的数据格式。
那这背后SpringMVC
又为我们做了哪些操作呢?别着急,相信读了后续文章你会对HttpMeesageConverter
有一个更加深刻的认识。
预备知识
在开始阅读本文之前,我们先来理解一下Servlet
下体系下的几个关键概念。在处理 HTTP
请求的过程中,通常需要解析请求体内容,并将返回结果设置到响应体。如果你有过Servlet
的开发经历,你一定会知道在 Servlet
标准中,对于类javax.servlet.ServletRequest
和 javax.servlet.ServletResponse
其内部分别会定义如下方法:
java
public ServletInputStream getInputStream() throws IOException;
public ServletOutputStream getOutputStream() throws IOException;
通过上面两个方法可以获取到请求体和响应体。这是因为ServletInputStream
和ServletOutputStream
分别继承 java
中的 InputStream
和 OutputStream
流对象,所以可以通过它们获取请求报文和设置响应报文。 进一步,在 Sping MVC
中,会将 Servlet
提供的请求和响应进行一层抽象 封装,便于操作读取和写入,再通过 HttpMessageConverter
消息转换机制来解析请求报文或者设置响应报文。
接下来,我们便看看HttpMessageConverter
身上到底蕴藏着那些我们所不知道的秘密。
寻找HttpMessageConverter
的处理入口
正如 "配置HttpMessageConverter
" 一节中提到的,当我们将HttpMessageConverter
相关配置文件加入到SpringMVC
的配置文件后,SpringMVC
会根据配置内容返回指定类型的数据信息。
此时你可能会想,通常我们定义控制层
返回的内容信息时,通常会返回一个java
对象,此时怎么配置这段信息后我的返回值类型就从java对象
变为Json
信息呢?
SpringMVC
内部究竟对返回值做了哪些处理呢?又该从何处着手分析呢?
我们在上一篇SpringMVC流程分析(六):为处理器进行合适的"适配"中提到,对于RequestMappingHandlerAdapter
中 handler
背后的逻辑无非就是调用被@ReqeustMapping
标注的方法
。此外,在处理过程中还会依赖一些额外的组件信息来完成参数解析,返回值处理
等额外的操作 。
(注:默认分析通过@RequestMapping
构建的处理器
,所以对于适配器信息我们重点关注RequestMappingHandlerAdapter
)
看到RequestMappingHandlerAdapter
在执行hanlder
方法时会包含对返回值处理
等额外的操作 这些信息,不知你是不是会有这样一种猜想。即如果我们要分析SpringMVC
内部是如何将返回的java
对象转为Json
格式的,我们应该关注RequestMappingHandlerAdapter
中handler
方法相关逻辑。因为其在执行方法完毕后会完成对返回值进行额外处理的操作。
事实上,在RequestMappingHandlerAdapter
中对于返回值进行额外处理的调用链如下所示,其中handleReturnValue
的作用就在于对返回值进行处理。
此处我们直接给出了RequestMappingHandlerAdapter
方法中handlerInternal
的调用链信息。为了方便理解,在如图所示的调用链中我们省略了一些其他方法的调用,因为我们的主要目的在于引入handleReturnValue
方法。
有关handlerInternal
方法的调用逻辑远比此复杂的多,感兴趣的可以自己翻看相关源码,在此我们便不再赘述。
继续沿着上述调用链你会进入到RequestResponseBodyMethodProcessor
的 handleReturnValue
方法中,而其该方法逻辑如下所示: writeWithMessageConverters
方法
RequestResponseBodyMethodProcessor
#handleReturnValue
java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// <1> 设置已处理
mavContainer.setRequestHandled(true);
// <2> 创建请求和响应
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// <3> 使用 HttpMessageConverter 对对象进行转换,并写入到响应
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
j
上述代码在<2>
处,会将请求封装成 ServletServerHttpRequest
和 ServletServerHttpResponse
两种类型,其中:
ServletServerHttpRequest
:实现了ServerHttpRequest
接口;ServletServerHttpResponse
实现ServerHttpResponse
接口
进一步,我们关注<3>
处的writeWithMessageConverters
方法,因为对于java
对象信息进行转换在此完成。
RequestResponseBodyMethodProcessor
#writeWithMessageConverters
事实上,对于writeWithMessageConverters
的调用又会进入到RequestResponseBodyMethodProcessor
父类 AbstractMessageConverterMethodProcessor
的writeWithMessageConverters
方法中
(注:此处调用链可能看着有些复杂,但别乱,无非就是继承
那些小事~~)
AbstractMessageConverterMethodProcessor
#writeWithMessageConverters
java
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// <1> 获得 body、valueType、targetType
Object body; Class<?> valueType; Type targetType;
// <3> 选择使用的 MediaType
MediaType selectedMediaType = null;
// <4> 如果匹配到,则进行写入逻辑
if (selectedMediaType != null) {
// <4.1> 移除 quality 。例如,application/json;q=0.8 移除后为 application/json
selectedMediaType = selectedMediaType.removeQualityValue();
// <4.2> 遍历 messageConverters 数组
for (HttpMessageConverter<?> converter : this.messageConverters) {
// <4.3> 判断 HttpMessageConverter 是否支持转换目标类型
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter
? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType)
: converter.canWrite(valueType, selectedMediaType)) {
// <5.2> body 非空,则进行写入
if (body != null) {
if (genericConverter != null) {
// 信息写入
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
} else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
// <5.4> return 返回,结束整个逻辑
return;
}
}
}
// ... 上面省略了大量代码
}
我们选取其中几个重要的方法进行介绍:
-
<4.2> 处,遍历所有的
HttpMessageConverter
实现类 -
<4.3> 处,调用当前
HttpMessageConverter
实现类的 canWrite 方法,判断是否支持写入 -
<5.2> 处,调用该
HttpMessageConverter
实现类的 **write
** 方法,进行写入
至此,相信你应该能明白一个大概了,其实SpringMVC
中对返回值所谓的类型转换
,本背后的逻辑原来全部依赖于HttpMessageConverter
的write
方法。我们前面那么多的铺垫,也就为了能引出此处的调用逻辑。
HttpMessageConverter 接口
HttpMessageConverter
是对消息转换器最高层次的接口抽象,描述了一个消息转换器的一般特征。其内部相关法法如下:
java
public interface HttpMessageConverter<T> {
/** 能否读取 */
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
/** 能够写入 */
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
结构关系
进一步,HttpMessageConverter
接口体系的结构如下:
在如图所示的UML
关系中,可以看到对于HttpMessageConverter
的实现类可以分为两大分支:
MappingJackson2HttpMessageConveter
用于Json
格式化数据的处理Jaxb2RootElementHttpMessageConverter
用于Xml
格式处理
(注:在后续分析中,我们以MappingJackson2HttpMessageConveter
这一脉络进行重点讨论,以分析SpringMVC
内部如何将返回值信息处理为Json
格式类型的数据)
使用示例
在开始分析MappingJackson2HttpMessageConveter
之前,我们先通过一个例子来直观的感受MappingJackson2HttpMessageConveter
的作用。假设现在我们有如下代码信息:
java
@RestController
public class UserController {
@Autowired
UserService userService;
@GetMapping(value = "/query")
public ResultInfo<List<User>> queryUser(@RequestBody String name) {
try {
return Result.success().data(userService.queryUserByName(name));
} catch (Exception e) {
return Result.fail(e);
}
}
}
当前端请发起一个请求路径为 GET /query
的HTTP
后,由于对方法添加了@RequestBody
注解,所以从请求体读请求报文的,可以设置请求体中的数据为 yiHang
,即此时我们的业务逻辑为获取到到名字为 yiHang
的所有用户的信息。
而Spring MVC
处理该请求时,会先进入到 RequestResponseBodyMethodProcessor
这个类获取方法入参,其中会通过 StringHttpMessageConverter
从请求体中读取 yiHang
数据,作为调用方法的入参。
在 Spring MVC
获取到方法的返回结果后,又会进入到RequestResponseBodyMethodProcessor
这个类,往响应体中写数据,而这背后的逻辑都是通过 MappingJackson2HttpMessageConverter
将 List<User>
返回结果写入响应。
总结下来,整个过程如下所示:
"顶级基类":AbstractHttpMessageConverter
AbstractHttpMessageConverter ,实现 HttpMessageConverter
接口,提供通用的骨架方法。
在之前分析writeWithMessageConverters
时的<5.2>
处有这样一句代码
java
((HttpMessageConverter) converter).write(body,
selectedMediaType,
outputMessage);
其逻辑为调用HttpMessageConverter
的write
方法,进而向前端写回响应内容,既然AbstractHttpMessageConverter
会实现HttpMessageConverter
接口,所以为了搞清楚HttpMessageConverter
如何将数据内容信息格式化为json
数据类型,我们应该关注HttpMessageConverter
实现类的write
方法。进一步,在AbstractHttpMessageConverter
中write
方法如下:
AbstractHttpMessageConverter
java
public final void write(final T t,
@Nullable MediaType contentType,
HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// .... 省略其他无关代码
// 抽象方法,交给子类实现,用以返回不同类型的格式数据信息
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
可以看到,对于AbstractHttpMessageConverter
方法而言,其在实现write
方法时,会将写出内容的处理逻辑交给 writeInternal
进行实现。进一步,其子类 AbstractJackson2HttpMessageConverter
而言,其内部的writeInternal
方法逻辑如下:
AbstractJackson2HttpMessageConverter
java
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// <1> 获取编码方式
// <1.1> 获取 Content-Type,例如 `application/json;charset=UTF-8`
// appliation/json;charset=utf-8
MediaType contentType = outputMessage.getHeaders().getContentType();
// <1.2> 从 Content-Type 获取编码方式,默认 UTF8
JsonEncoding encoding = getJsonEncoding(contentType);
// <2> 构建一个 Json 生成器 `generator`,指定`输出流(响应)`和编码
// 例如:UTF8JsonGenerator 对象(jackson-core 包)
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
try {
// <3> 设置前缀,默认没有
writePrefix(generator, object);
// <4> 获得方法的返回结果对象 `value`,返回结果类型 `javaType`
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
// <4.1> 如果返回结果对象是 MappingJacksonValue 类型,没使用过
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
// <4.2> 获取方法的返回结果的类型 `javaType`
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = getJavaType(type, null);
}
// <5> 创建 ObjectWriter 对象 `objectWriter`,没有特殊配置通过 `this.objectMapper.writer()` 生成
ObjectWriter objectWriter = (serializationView != null ?
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
// <6> 获取序列化配置
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
// <7> **【重点】**通过 `objectWriter` 将返回结果进行序列化,设置到 `generator` 中
// 多为fastjson中的序列化处理方式
objectWriter.writeValue(generator, value);
// <8> 设置后缀,默认没有
writeSuffix(generator, object);
// <9> 让 `generator` 刷出数据,以 Json 格式输出
// 也就是会往响应中刷出 Json 格式的返回结果
generator.flush();
}
}
对于上述代码我们重点关注的几个地方有如下几处:
<5>
创建 ObjectWriter 对象 objectWriter ,没有特殊配置通过 this.objectMapper.writer() 生成
<6>
获取序列化配置
<7>
【重点】 通过 objectWriter
将返回结果进行序列化,设置到 generator
中
<8>
调用 writeSuffix(JsonGenerator generator, Object object)
方法,设置后缀。
事实上,上述代码可以归结为如下的调用链信息:
具体实现:MappingJackson2HttpMessageConverter
MappingJackson2HttpMessageConverter
继承 AbstractJackson2HttpMessageConverter
抽象类,JSON
格式的消息读取或者写入,也就是我们熟悉的 @RequestBody
和 @ResponseBody
注解对应的 HttpMessageConverter
消息转换器
java
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
@Nullable
private String jsonPrefix;
public MappingJackson2HttpMessageConverter() {
this(Jackson2ObjectMapperBuilder.json().build());
}
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
public void setJsonPrefix(String jsonPrefix) {
this.jsonPrefix = jsonPrefix;
}
public void setPrefixJson(boolean prefixJson) {
this.jsonPrefix = (prefixJson ? ")]}', " : null);
}
@Override
protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
if (this.jsonPrefix != null) {
generator.writeRaw(this.jsonPrefix);
}
}
}
可以看到仅是添加了一个 jsonPrefix
属性,即Json
的前缀。除此之外,基本没有其他逻辑,这是因为相关处理逻辑都在父类中进行了定义和处理。
总结
在 Spring MVC
的 HttpMessageConverter
机制中可以领悟到类似的道理,一次请求报文和一次响应报文,分别被抽象为一个请求消息 HttpInputMessage
和一个响应消息 HttpOutputMessage
在处理请求时,选取合适的 HttpMessageConverter
消息转换器将请求报文绑定为方法中的形参对象,同一个对象就有可能出现多种不同的消息形式,比如 json
和 xml
,同样,当响应请求时,方法的返回值也同样可能被返回为不同的消息形式,比如 json
和 xml
。
而在 Spring MVC
中,针对不同的消息形式,有不同的 HttpMessageConverter
实现类来处理各种消息形式。但是,只要这些消息所蕴含的"有效信息"是一致的,那么各种不同的消息转换器,都会生成同样的转换结果。至于各种消息间解析细节的不同,就被屏蔽在不同的 HttpMessageConverter
实现类中了。