更新于:2020-04-25
一、Introduce
Spring Boot provides integration with three JSON mapping libraries:
- Gson
- Jackson
- JSON-B
Jackson is the preferred and default library.
SpringBoot 默认提供了三种 json 支持,默认使用 Jackson
作为支持。
除了官方提到的这三种 json 框架,开发中常用的还有 Fast-Json
等。
二、Spring MVC 与 Json 解析
Spring-MVC请求参数和响应结果解析 中提到了,Spring MVC 框架
针对请求处理前会对请求入参进行参数处理,针对响应结果同样会进行响应的处理。
其中针对 JSON 格式入参和 JSON 格式返回的处理都会交由相关的 JSON 解析框架来完成。
下面以 SpringBoot 默认的 Jackson 解析框架进行分析。
一个简单的测试 API
2.1、Json 请求入参
请求入参会在 ⑦ 被解析器集合解析。
Json 入参对应的解析器为 RequestResponseBodyMethodProcessor
。
RequestResponseBodyMethodProcessor
解析器的入参解析流程 UML 如下
Json 请求入参的解析委派给了 HttpMessageConverter
实现类来完成,jackson 对应的类为 MappingJackson2HttpMessageConverter
MappingJackson2HttpMessageConverter
类结构
MappingJackson2HttpMessageConverter
相关逻辑实现在其父类 AbstractJackson2HttpMessageConverter
中完成
AbstractJackson2HttpMessageConverter
相关代码
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
@Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) { return canRead(clazz, null, mediaType); }
@Override
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
// 仅支持 application/json 和 application/*+json 格式
if (!canRead(mediaType)) {
return false;
}
JavaType javaType = getJavaType(type, contextClass);
AtomicReference<Throwable> causeRef = new AtomicReference<>();
// 能够被序列化
if (this.objectMapper.canDeserialize(javaType, causeRef)) {
return true;
}
logWarningIfNecessary(javaType, causeRef.get());
return false;
}
@Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// 这里为入参对象的类型,如: DemoModel
JavaType javaType = getJavaType(type, contextClass);
// 调用 Jackson 原生方法进行参数转换
return readJavaType(javaType, inputMessage);
}
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
}
}
以上逻辑主要两个方法
canRead
判定是否能够被该解析器解析 条件一:Content-Type 格式为:application/json 或 application/*+json 格式 条件二:映射的 Java 对象允许被反序列化。
read
直接使用 Jackson 原生的
ObjectMapper
API
2.2、Json 响应结果解析
⑨ 得到响应结果后在 ⑩ 进行相关的响应结果类型解析。
Json 参数返回结果的解析器同请求入参解析器都为 RequestResponseBodyMethodProcessor
。
RequestResponseBodyMethodProcessor
类结构如下:
通过类图,能够知道 RequestResponseBodyMethodProcessor
不仅仅支持请求入参解析,同样支持响应结果解析。
RequestResponseBodyMethodProcessor
解析器的响应结果解析流程 UML 如下
Json 响应结果的解析同样委派给了 HttpMessageConverter
实现类来完成,jackson 对应的类为 MappingJackson2HttpMessageConverter
不过不同的是 响应结果解析调用的是 write 方法。主要逻辑处理同样是在其抽象父类 AbstractJackson2HttpMessageConverter
和 AbstractGenericHttpMessageConverter
中实现
AbstractJackson2HttpMessageConverter
canWrite
相关代码
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
// 此时 mediaType ==null 所以返回 true
if (!canWrite(mediaType)) {
return false;
}
AtomicReference<Throwable> causeRef = new AtomicReference<>();
if (this.objectMapper.canSerialize(clazz, causeRef)) {
return true;
}
logWarningIfNecessary(clazz, causeRef.get());
return false;
}
}
主要逻辑 canWrite
判断响应结果是否能够被该解析器解析 条件: 该类能够被序列化
AbstractGenericHttpMessageConverter
write
相关代码
public abstract class AbstractGenericHttpMessageConverter<T> extends AbstractHttpMessageConverter<T> implements GenericHttpMessageConverter<T> {
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
// 调用的 jackson 原生的 write 方法
writeInternal(t, type, outputMessage);
outputMessage.getBody().flush();
}
}
}
主要逻辑 write
最后同样调用的 Jackson 原生的 API。将结果写入到流中。
三、SpringBoot 更换 Json解析框架
以替换为 Gson 为例
方案一:剔除 Jackson 依赖
分析
在 Spring MVC 配置类 WebMvcConfigurationSupport
中的相关代码如下:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
private static final boolean romePresent;
private static final boolean jaxb2Present;
private static final boolean jackson2Present;
private static final boolean jackson2XmlPresent;
private static final boolean jackson2SmilePresent;
private static final boolean jackson2CborPresent;
private static final boolean gsonPresent;
private static final boolean jsonbPresent;
static {
ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}
// 添加默认的 HttpMessageConverter
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
if (romePresent) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
......
}
else if (jaxb2Present) {
......
}
if (jackson2Present) {
......
}
else if (gsonPresent) {
......
}
else if (jsonbPresent) {
......
}
if (jackson2SmilePresent) {
......
}
if (jackson2CborPresent) {
......
}
}
}
在上面的分析中,Json 请求入参和json 响应结果最终都是有相关的 HttpMessageConverter
来解析的,而 HttpMessageConverter
匹配,在项目中,多个 HttpMessageConverter
是以 List 的方式存在的,也就是说,多个拥有相同解析功能的 HttpMessageConverter
,最终有谁来进行解析,取决于其在 List 中的先后顺序。况且在上述代码中能够知道,在存在有 jackson 相关依赖时,如:gson 之类的解析器并不为被注册。
执行操作
step1、移除 pom 中的关于 jackson
的依赖,例如下图:
step2、pom 中增加 gson
依赖,如下图:
方案二:
方案一,存在一个很大的问题,就是需要将依赖中所有的 jackson 相关的依赖提出,剔除不干净,同样会导致最终注册也是 Jackson 的解析器。
该方案直接通过 Spring MVC 自己手动配置,免除提出 jackson
依赖的繁琐工作
配置类如下:
/**
* <p> web 配置类 </p>
*
* @Author 彳失口亍
*/
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 剔除 MappingJackson2HttpMessageConverter
converters.removeIf(httpMessageConverter -> httpMessageConverter instanceof MappingJackson2HttpMessageConverter);
// 手动添加 GsonHttpMessageConverter
converters.add(new GsonHttpMessageConverter());
}
}
直接剔除 jackson 解析器 MappingJackson2HttpMessageConverter
,然后添加自己需要的解析器即可。