更新于:2020-04-25

一、Introduce

Spring Boot 官方文档介绍

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 等。

几种常见的JSON工具
几种常见的JSON工具性能对比

二、Spring MVC 与 Json 解析

Spring-MVC请求参数和响应结果解析 中提到了,Spring MVC 框架
针对请求处理前会对请求入参进行参数处理,针对响应结果同样会进行响应的处理。

其中针对 JSON 格式入参和 JSON 格式返回的处理都会交由相关的 JSON 解析框架来完成。

下面以 SpringBoot 默认的 Jackson 解析框架进行分析。

一个简单的测试 API
01.png

源码追踪 UML 如下
02.png

2.1、Json 请求入参

请求入参会在 ⑦ 被解析器集合解析。

Json 入参对应的解析器为 RequestResponseBodyMethodProcessor

RequestResponseBodyMethodProcessor 解析器的入参解析流程 UML 如下
03.png

Json 请求入参的解析委派给了 HttpMessageConverter 实现类来完成,jackson 对应的类为 MappingJackson2HttpMessageConverter

MappingJackson2HttpMessageConverter 类结构

04.png
MappingJackson2HttpMessageConverter 相关逻辑实现在其父类 AbstractJackson2HttpMessageConverter 中完成

AbstractJackson2HttpMessageConverter 相关代码

  1. public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
  2. @Override
  3. public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) { return canRead(clazz, null, mediaType); }
  4. @Override
  5. public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
  6. // 仅支持 application/json 和 application/*+json 格式
  7. if (!canRead(mediaType)) {
  8. return false;
  9. }
  10. JavaType javaType = getJavaType(type, contextClass);
  11. AtomicReference<Throwable> causeRef = new AtomicReference<>();
  12. // 能够被序列化
  13. if (this.objectMapper.canDeserialize(javaType, causeRef)) {
  14. return true;
  15. }
  16. logWarningIfNecessary(javaType, causeRef.get());
  17. return false;
  18. }
  19. @Override
  20. public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
  21. throws IOException, HttpMessageNotReadableException {
  22. // 这里为入参对象的类型,如: DemoModel
  23. JavaType javaType = getJavaType(type, contextClass);
  24. // 调用 Jackson 原生方法进行参数转换
  25. return readJavaType(javaType, inputMessage);
  26. }
  27. private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
  28. return this.objectMapper.readValue(inputMessage.getBody(), javaType);
  29. }
  30. }

以上逻辑主要两个方法

  • canRead

    判定是否能够被该解析器解析 条件一:Content-Type 格式为:application/json 或 application/*+json 格式 条件二:映射的 Java 对象允许被反序列化。

  • read

    直接使用 Jackson 原生的 ObjectMapper API

2.2、Json 响应结果解析

⑨ 得到响应结果后在 ⑩ 进行相关的响应结果类型解析。

Json 参数返回结果的解析器同请求入参解析器都为 RequestResponseBodyMethodProcessor

RequestResponseBodyMethodProcessor 类结构如下:
05.png

通过类图,能够知道 RequestResponseBodyMethodProcessor 不仅仅支持请求入参解析,同样支持响应结果解析。

RequestResponseBodyMethodProcessor 解析器的响应结果解析流程 UML 如下
06.png
Json 响应结果的解析同样委派给了 HttpMessageConverter 实现类来完成,jackson 对应的类为 MappingJackson2HttpMessageConverter

不过不同的是 响应结果解析调用的是 write 方法。主要逻辑处理同样是在其抽象父类 AbstractJackson2HttpMessageConverterAbstractGenericHttpMessageConverter 中实现

AbstractJackson2HttpMessageConverter canWrite 相关代码

  1. public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
  2. @Override
  3. public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
  4. // 此时 mediaType ==null 所以返回 true
  5. if (!canWrite(mediaType)) {
  6. return false;
  7. }
  8. AtomicReference<Throwable> causeRef = new AtomicReference<>();
  9. if (this.objectMapper.canSerialize(clazz, causeRef)) {
  10. return true;
  11. }
  12. logWarningIfNecessary(clazz, causeRef.get());
  13. return false;
  14. }
  15. }

主要逻辑 canWrite

判断响应结果是否能够被该解析器解析 条件: 该类能够被序列化

AbstractGenericHttpMessageConverter write 相关代码

  1. public abstract class AbstractGenericHttpMessageConverter<T> extends AbstractHttpMessageConverter<T> implements GenericHttpMessageConverter<T> {
  2. public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
  3. HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
  4. final HttpHeaders headers = outputMessage.getHeaders();
  5. addDefaultHeaders(headers, t, contentType);
  6. if (outputMessage instanceof StreamingHttpOutputMessage) {
  7. StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
  8. streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
  9. @Override
  10. public OutputStream getBody() {
  11. return outputStream;
  12. }
  13. @Override
  14. public HttpHeaders getHeaders() {
  15. return headers;
  16. }
  17. }));
  18. }
  19. else {
  20. // 调用的 jackson 原生的 write 方法
  21. writeInternal(t, type, outputMessage);
  22. outputMessage.getBody().flush();
  23. }
  24. }
  25. }

主要逻辑 write

最后同样调用的 Jackson 原生的 API。将结果写入到流中。

三、SpringBoot 更换 Json解析框架

以替换为 Gson 为例

方案一:剔除 Jackson 依赖

分析

在 Spring MVC 配置类 WebMvcConfigurationSupport 中的相关代码如下:

  1. public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
  2. private static final boolean romePresent;
  3. private static final boolean jaxb2Present;
  4. private static final boolean jackson2Present;
  5. private static final boolean jackson2XmlPresent;
  6. private static final boolean jackson2SmilePresent;
  7. private static final boolean jackson2CborPresent;
  8. private static final boolean gsonPresent;
  9. private static final boolean jsonbPresent;
  10. static {
  11. ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
  12. romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
  13. jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
  14. jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
  15. ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
  16. jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
  17. jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
  18. jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
  19. gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
  20. jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
  21. }
  22. // 添加默认的 HttpMessageConverter
  23. protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
  24. if (romePresent) {
  25. messageConverters.add(new AtomFeedHttpMessageConverter());
  26. messageConverters.add(new RssChannelHttpMessageConverter());
  27. }
  28. if (jackson2XmlPresent) {
  29. ......
  30. }
  31. else if (jaxb2Present) {
  32. ......
  33. }
  34. if (jackson2Present) {
  35. ......
  36. }
  37. else if (gsonPresent) {
  38. ......
  39. }
  40. else if (jsonbPresent) {
  41. ......
  42. }
  43. if (jackson2SmilePresent) {
  44. ......
  45. }
  46. if (jackson2CborPresent) {
  47. ......
  48. }
  49. }
  50. }

在上面的分析中,Json 请求入参和json 响应结果最终都是有相关的 HttpMessageConverter 来解析的,而 HttpMessageConverter 匹配,在项目中,多个 HttpMessageConverter 是以 List 的方式存在的,也就是说,多个拥有相同解析功能的 HttpMessageConverter ,最终有谁来进行解析,取决于其在 List 中的先后顺序。况且在上述代码中能够知道,在存在有 jackson 相关依赖时,如:gson 之类的解析器并不为被注册。

执行操作

step1、移除 pom 中的关于 jackson 的依赖,例如下图:
07.png
step2、pom 中增加 gson 依赖,如下图:
08.png

方案二:

方案一,存在一个很大的问题,就是需要将依赖中所有的 jackson 相关的依赖提出,剔除不干净,同样会导致最终注册也是 Jackson 的解析器。

该方案直接通过 Spring MVC 自己手动配置,免除提出 jackson 依赖的繁琐工作

配置类如下:

  1. /**
  2. * <p> web 配置类 </p>
  3. *
  4. * @Author 彳失口亍
  5. */
  6. @Configuration
  7. public class WebConfiguration implements WebMvcConfigurer {
  8. @Override
  9. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  10. // 剔除 MappingJackson2HttpMessageConverter
  11. converters.removeIf(httpMessageConverter -> httpMessageConverter instanceof MappingJackson2HttpMessageConverter);
  12. // 手动添加 GsonHttpMessageConverter
  13. converters.add(new GsonHttpMessageConverter());
  14. }
  15. }

直接剔除 jackson 解析器 MappingJackson2HttpMessageConverter ,然后添加自己需要的解析器即可。