更新于: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

源码追踪 UML 如下

# 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

# Json 响应结果解析

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

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

RequestResponseBodyMethodProcessor 类结构如下:

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

RequestResponseBodyMethodProcessor 解析器的响应结果解析流程 UML 如下

Json 响应结果的解析同样委派给了 HttpMessageConverter 实现类来完成,jackson 对应的类为 MappingJackson2HttpMessageConverter

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

# 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 ,然后添加自己需要的解析器即可。

精彩内容推送,请关注公众号!
最近更新时间: 4/22/2020, 7:22:09 PM