有道无术,术尚可求,有术无道,止于术。
本系列Jackson 版本 2.17.0
源码地址:https://gitee.com/pearl-organization/study-jaskson-demo
文章目录
- 1. 前言
- 2. Spring Web
- 3. Jackson2ObjectMapperBuilder
- 4. Jackson2ObjectMapperFactoryBean
- 5. MappingJackson2HttpMessageConverter
1. 前言
Spring
生态使用Jackson
作为默认的JSON
处理框架,这些年随着Spring
的大发异彩和Jackson
本身的优越特性,已成为世界上最流行的JSON
库。
接下来,本系列会讲解Spring MVC
、Spring Boot
中如何使用Jackson
,以及Spring
对其进行扩展增强的相关源码,所以需要读者有一定的Spring
、Spring Boot
开发经验。
2. Spring Web
Spring MVC
对于后端开发人员来说已经很熟悉了,它是一个构建在Servlet
之上的一个Web
框架,而Spring Web
是Spring MVC
的基础模块(还有一个Spring Flux
),它们同属于spring-framework
框架。
Web
框架在处理HTTP
请求响应时,需要将请求报文反序列化为Java
对象进行接收处理,在响应阶段,需要将Java
对象反序列化为报文写出,所以需要依赖JSON
框架去处理。
Spring Web
中可以看到引入了Jackson
、GJson
:
在spring-web
模块的org.springframework.http.converter.json
包下,可以看到Json
集成的代码:
集成Jackson
的主要有:
Jackson2ObjectMapperBuilder
:ObjectMapper
构建器Jackson2ObjectMapperFactoryBean
:FactoryBean
创建和管理ObjectMapper
对象MappingJackson2HttpMessageConverter
:基于Jackson
的消息转换器SpringHandlerInstantiator
:Spring
容器创建Jackson
组件,例如JsonSerializer
、JsonDeserializer
、KeyDeserializer
…JacksonModulesRuntimeHints
:Spring AOT
所需的RuntimeHints
(运行时提示)
3. Jackson2ObjectMapperBuilder
Jackson2ObjectMapperBuilder
从名字上看已经很好理解,它是一个Jackson
的ObjectMapper
构建器,提供了Fluent API
来定制ObjectMapper
的默认属性并构建实例。
在之前我们都是通过new
的方式创建ObjectMapper
实例,现在可以使用构建者模式,这也是Spring
自身使用和推荐使用的方式。
它的构造方法是public
的,说明可以直接new
创建Jackson2ObjectMapperBuilder
:
public Jackson2ObjectMapperBuilder() {}
提供了多个创建不同数据类型支持的静态方法:
// 构建 Jackson2ObjectMapperBuilderpublic static Jackson2ObjectMapperBuilder json() {return new Jackson2ObjectMapperBuilder();}// 构建 Jackson2ObjectMapperBuilder,并设置需要创建 XmlMapper,需要引入`jackson-dataformat-xml`模块,用于支持`XML`数据格式public static Jackson2ObjectMapperBuilder xml() {return new Jackson2ObjectMapperBuilder().createXmlMapper(true);}// 指定 JsonFactory-》SmileFactory,需要引入`jackson-dataformat-smile`模块,用于支持`Smile`数据格式public static Jackson2ObjectMapperBuilder smile() {return new Jackson2ObjectMapperBuilder().factory(new SmileFactoryInitializer().create());}// 指定 JsonFactory-》CBORFactory-》需要引入`jackson-dataformat-cbor`模块,用于支持`CBOR`数据格式public static Jackson2ObjectMapperBuilder cbor() {return new Jackson2ObjectMapperBuilder().factory(new CborFactoryInitializer().create());}
一般场景中,使用json()
创建即可:
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
此外还提供了很多方法,例如自定义序列化/反序列化器、模块注册、启用/禁用特征、 混合注解等等,和ObjectMapper
本身的方法大多类似,这里就不一一赘述了。
// 配置自定义序列化器(多个)public Jackson2ObjectMapperBuilder serializers(JsonSerializer<?>... serializers) {for (JsonSerializer<?> serializer : serializers) {Class<?> handledType = serializer.handledType();if (handledType == null || handledType == Object.class) {throw new IllegalArgumentException("Unknown handled type in " + serializer.getClass().getName());}this.serializers.put(serializer.handledType(), serializer);}return this;}// 模块注册public Jackson2ObjectMapperBuilder modules(Module... modules) {return modules(Arrays.asList(modules));}/*** 启用特征** @see com.fasterxml.jackson.core.JsonParser.Feature* @see com.fasterxml.jackson.core.JsonGenerator.Feature* @see com.fasterxml.jackson.databind.SerializationFeature* @see com.fasterxml.jackson.databind.DeserializationFeature* @see com.fasterxml.jackson.databind.MapperFeature*/public Jackson2ObjectMapperBuilder featuresToEnable(Object... featuresToEnable) {for (Object feature : featuresToEnable) {this.features.put(feature, Boolean.TRUE);}return this;}
需要重点关注的方法有build
、configure
,build
方法用于构建一个ObjectMapper
实例,每次调用都会返回一个新的对象,对于处理不同JSON
格式或需要不同序列化/反序列化行为的场景,可以构建新的实例来处理。
@SuppressWarnings("unchecked")public <T extends ObjectMapper> T build() {// 创建实例ObjectMapper mapper;if (this.createXmlMapper) {// 需要创建XmlMapper,则使用 XmlFactorymapper = (this.defaultUseWrapper != null ?new XmlObjectMapperInitializer().create(this.defaultUseWrapper, this.factory) :new XmlObjectMapperInitializer().create(this.factory));} else {// 不需要创建XmlMapper,则使用指定的工厂mapper = (this.factory != null ? new ObjectMapper(this.factory) : new ObjectMapper());}// 配置configure(mapper);return (T) mapper;}
configure
方法用于将成员属性都设置给已存在的ObjectMapper
实例:
public void configure(ObjectMapper objectMapper) {Assert.notNull(objectMapper, "ObjectMapper must not be null");// 注册模块MultiValueMap<Object, Module> modulesToRegister = new LinkedMultiValueMap<>();if (this.findModulesViaServiceLoader) {ObjectMapper.findModules(this.moduleClassLoader).forEach(module -> registerModule(module, modulesToRegister));} else if (this.findWellKnownModules : modulesToRegister.values()) {modules.addAll(nestedModules);}objectMapper.registerModules(modules);// 设置时间日期格式化if (this.dateFormat != null) {objectMapper.setDateFormat(this.dateFormat);}// 设置 Localeif (this.locale != null) {objectMapper.setLocale(this.locale);}// 设置 TimeZone if (this.timeZone != null) {objectMapper.setTimeZone(this.timeZone);}// 设置 注解解析器if (this.annotationIntrospector != null) {objectMapper.setAnnotationIntrospector(this.annotationIntrospector);}// 设置属性名称策略if (this.propertyNamingStrategy != null) {objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy);}// 省略其他.........if (this.configurer != null) {this.configurer.accept(objectMapper);}}
4. Jackson2ObjectMapperFactoryBean
Jackson2ObjectMapperFactoryBean
提供了基于Spring
框架FactoryBean
机制创建和配置ObjectMapper
实例。
示例如下:
@Configuration
public class MvcConfig {@Beanpublic Jackson2ObjectMapperFactoryBean jacksonObjectMapper() {Jackson2ObjectMapperFactoryBean factoryBean = new Jackson2ObjectMapperFactoryBean();factoryBean.setIndentOutput(true);factoryBean.setSimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 还可以设置其他属性,如自定义的序列化器、反序列化器等return factoryBean;}
}
5. MappingJackson2HttpMessageConverter
MappingJackson2HttpMessageConverter
即基于Jackson2
的消息转换器实现,其本身并没有多少代码,读写能力来自于父类。
继承关系如下:
HttpMessageConverter
GenericHttpMessageConverter
AbstractGenericHttpMessageConverter
AbstractJackson2HttpMessageConverter
MappingJackson2HttpMessageConverter
顶级接口HttpMessageConverter
是Spring Web
定义的一个HTTP
消息转换器。负责将请求和响应的数据从Java
对象转换为HTTP
协议所需的格式,或者将HTTP
协议中的数据转换为Java
对象,是Spring
框架中用于处理HTTP
请求和响应数据转换的重要组件。
直接父类AbstractJackson2HttpMessageConverter
定义了泛型为Object
,其内部维护了多个ObjectMapper
对象:
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> // 默认的ObjectMapperprotected ObjectMapper defaultObjectMapper;@Nullable// registerObjectMappersForType 方法注册的多个ObjectMapperprivate Map<Class<?>, Map<MediaType, ObjectMapper>> objectMapperRegistrations;
可以调用构造方法或者set
方法设置默认的ObjectMapper
:
protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper) {this.defaultObjectMapper = objectMapper;DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));this.ssePrettyPrinter = prettyPrinter;}protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType supportedMediaType) {this(objectMapper);this.setSupportedMediaTypes(Collections.singletonList(supportedMediaType));}protected AbstractJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes) {this(objectMapper);this.setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));}public void setObjectMapper(ObjectMapper objectMapper) {Assert.notNull(objectMapper, "ObjectMapper must not be null");this.defaultObjectMapper = objectMapper;this.configurePrettyPrint();}
重写了父类的canRead
、canWrite
方法,例如canRead
,首先调用父类的 canRead
方法,是否支持当前 MediaType
,然后查询并判断ObjectMapper
是否支持反序列化当前类型:
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {// 调用父类的 canRead 方法,是否支持当前 MediaTypeif (!this.canRead(mediaType)) {return false;} else {// 根据JAVA Type 类型,转换为 Jackson 的 JavaType JavaType javaType = this.getJavaType(type, contextClass);// 查询一个可用的 ObjectMapper ObjectMapper objectMapper = this.selectObjectMapper(javaType.getRawClass(), mediaType);if (objectMapper == null) {return false;} else {// ObjectMapper 是否可反序列化当前类型 AtomicReference<Throwable> causeRef = new AtomicReference();if (objectMapper.canDeserialize(javaType, causeRef)) {return true;} else {this.logWarningIfNecessary(javaType, (Throwable)causeRef.get());return false;}}}}
最重要的是真正实现了转换器最核心的读写方法,例如read
方法中并不是直接通过ObjectMapper
进行读操作,而是通过ObjectMapper
创建了底层的ObjectReader
去执行读操作。
// 将 HTTP 输入消息,转换为 JAVA 对象public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {JavaType javaType = this.getJavaType(type, contextClass);return this.readJavaType(javaType, inputMessage);}private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {// 获取 Content-TypeMediaType contentType = inputMessage.getHeaders().getContentType();// 获取 Charset Charset charset = this.getCharset(contentType);// 查询可用的 ObjectMapper ObjectMapper objectMapper = this.selectObjectMapper(javaType.getRawClass(), contentType);Assert.state(objectMapper != null, () -> {return "No ObjectMapper for " + javaType;});boolean isUnicode = ENCODINGS.containsKey(charset.name()) || "UTF-16".equals(charset.name()) || "UTF-32".equals(charset.name());try {// 获取输入流 InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody());if (inputMessage instanceof MappingJacksonInputMessage) {// 支持 Jackson 视图 MappingJacksonInputMessage mappingJacksonInputMessage = (MappingJacksonInputMessage)inputMessage;Class<?> deserializationView = mappingJacksonInputMessage.getDeserializationView();if (deserializationView != null) {ObjectReader objectReader = objectMapper.readerWithView(deserializationView).forType(javaType);objectReader = this.customizeReader(objectReader, javaType);if (isUnicode) {return objectReader.readValue(inputStream);}Reader reader = new InputStreamReader(inputStream, charset);return objectReader.readValue(reader);}}// 使用 `ObjectMapper` 底层的`ObjectReader`读取ObjectReader objectReader = objectMapper.reader().forType(javaType);objectReader = this.customizeReader(objectReader, javaType);if (isUnicode) {return objectReader.readValue(inputStream);} else {Reader reader = new InputStreamReader(inputStream, charset);return objectReader.readValue(reader);}} catch (InvalidDefinitionException var12) {throw new HttpMessageConversionException("Type definition error: " + var12.getType(), var12);} catch (JsonProcessingException var13) {throw new HttpMessageNotReadableException("JSON parse error: " + var13.getOriginalMessage(), var13, inputMessage);}}