在Spring中的Jackson ObjectMapper生命周期。

huangapple go评论71阅读模式
英文:

Lifecycle of Jackson ObjectMapper in Spring

问题

环境:

我使用的是 Spring Boot 版本 2.3.3.RELEASE


问题:

我使用 @JsonComponent 注解注册自定义反序列化器。

在一些模块和测试中,我使用 @Autowire 注入了 jackson 的 ObjectMapper

问题1:我无法在 @JsonComponent 中使用 @Autowire 注入 ObjectMapper,否则 @JsonComponent 将被完全忽略(没有错误消息)。

问题2:我希望在 @JsonComponent 中拥有与其他组件中通过 Autowire 获得的相同的 ObjectMapper 实例(或者至少具有相同配置的 ObjectMapper)。


示例:

由于我不想为某些字段进行自定义反序列化,我使用 ObjectMapper 在自定义反序列化器中使用 convertValue() 方法。

我注意到,在任何一个自定义反序列化器中都无法使用 @Autowire 注入 ObjectMapper,否则其他通过 Autowire 在我的应用程序中注入的 ObjectMappers 将无法捕获配置(因此反序列化器无法正确注册)。

我在反序列化器中通过 (ObjectMapper) jsonParser.getCodec(); 获取 ObjectMapper。

但是,这个 ObjectMapper 配置不正确(例如,类/方法上的注解被忽略)。

我想按照以下方式反序列化我的对象:

@Test
void testDeserializeFoo() throws IOException {
    ObjectMapper om = new ObjectMapper(); // 这是通过 jsonParser 在反序列化器中获得的,但是由于缺乏配置,它的行为是相同的
    InputStream is = getClass().getResourceAsStream("/json/foo.json");
    JsonNode fooNode = om.readTree(is);
    Foo foo =  om.convertValue(fooNode, Foo.class);
    log.info("Foo: " + foo);
}

Foo 的构造函数带有 @JsonCreator 注解。

问题是,我从自定义反序列化器中的解析器获取的 ObjectMapper 会忽略这个注解。这导致了以下错误消息:

类型 Foo 的类型定义无效:
参数 #0 没有属性名称,不可注入:无法用作创建者 [Foo 的构造函数,注解:{接口 com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}]

是否可能在我的自定义反序列化器中使用与在其他组件中通过 Autowired 使用的相同的 ObjectMapper 实例?


编辑:

可能的解决方案 / 测试:

我以这种方式测试我的反序列化器:

private JsonParser getJsonParser(String resource) throws IOException {
    InputStream src = getInputStream(resource);
    return objectMapper.createParser(src);
}

@Test
void testDeserializeFoo() throws IOException {
    JsonParser parser = getJsonParser("/json/foo.json");
    Foo foo = deserializer.deserialize(parser, null);
    System.out.println(foo);
}

问题在于 getParser() 方法中的 objectMapper 每次都是一个新实例。现在我使用了 autowired 的 objectMapper。但是这会产生什么影响呢?


结论:

我希望在我的 JsonComponents(自定义反序列化器)中使用与 Autowired ObjectMapper 相同的实例,或者至少具有相同配置的 ObjectMapper 实例。

配置指的是:启用的 Jackson 特性/类、方法、字段上的注解配置/已注册的模块和 JsonComponents。

有人能解释一下吗:

  • ObjectMapper 在 Spring 中如何配置(什么时候在 ObjectMapper 上注册注解配置(JsonCreator、JsonProperty)、特性配置(比如接受大写)以及应用程序.yml 中的配置、自定义 JsonComponents 等)?
  • 这个 objectMapper 实例的生命周期是什么?
  • ContextConfiguration、DeserializationConfiguration 的生命周期是什么?
  • Spring 和 Jackson 上下文之间的关系是什么?

我寻找了相关的主题,其他相关的回答包括:

  • Spring 和 Jackson 上下文不是相同的(但是为什么,这是什么意思?)
  • 您需要一个 HandlerInstantiator 来进行自动装配(我认为这已经是我的 Spring 版本的默认设置了,而且这并不能解释为什么当在其中一个反序列化器中进行 ObjectMapper 自动装配时,我的自定义反序列化器没有被正确捕获,也没有任何错误消息)。
英文:

Environment:

I use spring boot version 2.3.3.RELEASE


Problem

I register custom deserializers with @JsonComponent annotation.

In some modules and tests I @Autowire jacksons ObjectMapper.

Problem 1: I cannot @Autowire ObjectMapper in @JsonComponent or the @JsonComponent will be ignored completely (without error message)

Problem 2: I want to have the same instance of ObjectMapper in @JsonComponent (or at least OM with same configuration) that I can obtain by Autowire in other Components


Example

Since I don't want custom deserialization for some fields I use the ObjectMapper to use convertValue() in my custom deserializers.

I noticed, that I cannot @Autowire the ObjectMapper in any of my Deserializers or the Configuration will not be picked up for other ObjectMappers that are autowired in my Application (So the Deserializers are not registered correctly)

I'm obtaining the ObjectMapper in the Deserializer with (ObjectMapper) jsonParser.getCodec();

But this ObjectMapper is not configured correctly (meaning e.g. annotations on class/methods are ignored).

I want to deserialize my object as follows:

@Test
void testDeserializeFoo() throws IOException {
    ObjectMapper om = new ObjectMapper(); // this is obtained by jsonParser in deserializer but it behaves the same, since the ObjectMapper lacks the configuration
    InputStream is = getClass().getResourceAsStream("/json/foo.json");
    JsonNode fooNode = om.readTree(is);
    Foo foo =  om.convertValue(fooNode, Foo.class);
    log.info("Foo: " + foo);
}

The constructor of Foo is annotated with @JsonCreator.

The Problem is, that this annotation is ignored by the ObjectMapper I get from the parser in the custom Deserializer. This results in the following error message:

> Invalid type definition for type
> Foo:
> Argument #0 has no property name, is not Injectable: can not use as
> Creator [constructor for
> Foo,
> annotations: {interface
> com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}]

Is it possible to have the same instance of ObjectMapper in my custom Deserializer that is used in other Components when Autowired?


Edit

Possible Solution / Test:

I was testing my deserializer like this:

private JsonParser getJsonParser(String resource) throws IOException {
        InputStream src = getInputStream(resource);
        return objectMapper.createParser(src);
}
    
@Test
void testDeserializeFoo() throws IOException {
    JsonParser parser = getJsonParser("/json/foo.json");
    Foo foo = deserializer.deserialize(parser, null);
    System.out.println(foo);
}

The problem was that the objectMapper in getParser() method was a new instance all the time. Now I use the autowired objectMapper. But how does this have any effect?


Conclusion:

I want to use the same instance of ObjectMapper in my JsonComponents (custom deserializers) as the Autowired ObjectMapper (which is provided by spring in other Components), or at least an ObjectMapper with the same configuration.

With configuration I mean: Enabled Jackson Features / Annotation Configurations on classes, methods, fields / registered modules and JsonComponents

Can someone please explain:

  • how and when ObjectMapper is configured in Spring (when are
    Annotation configurations (JsonCreator, JsonProperty), Feature
    configurations (like accepting upper case) in application.yml, Custom
    JsonComponents etc registered on an ObjectMapper
  • what is the lifecycle of this objectMapper instance?
  • What is the lifecycle of a ContextConfiguration,
    DeserializationConfiguration?
  • How does Spring and Jackson context relate to each other?

I looked for related topics, other answers related where:

  • Spring and Jackson context are not the same (but why, what does this mean?)
  • You need a HandlerInstantiator to Autowire (this is already default in my spring version I assume, also this does not explain why my custom deserializers are not picked up without any error messages when autowiring ObjectMapper in one of the deserializers)

答案1

得分: 1

一般而言,您可以使用Jackson2ObjectMapperBuilder和build()来生成新的对象映射器实例,并应用所有最近的设置。默认的ObjectMapper由JackonsObjectMapperConfiguration在JackonAutoConfiguration中提供。所有jackson2对象映射器构建器实例都是由JacksonObjectMapperBuilderConfiguration在JacksonAutoConfiguration中创建的。有关配置对象映射器的详细信息可以在这里找到。

如果您不喜欢JacksonAutoConfiguration中的标准默认设置,您还可以使用Jackson2ObjectMapperBuilderCustomizerConfiguration来进一步自定义jackson2对象映射器构建器。

查看这个答案 - 它将回答您的其他问题。

谈到问题 - 在使用jackson2objectbuilder/build或自动装配对象映射器进行使用时,如果使用JsonComponent注解进行注释,就会出现相同的问题。所有这些注解都由JacksonAutoConfiguration中定义的JsonComponentModule bean扫描,并在默认对象映射器中注册,以及由jackson2对象映射器构建器创建的所有实例。因此,不可能同时使用尚未构建的对象映射器并注册ser/deser。您是对的,Spring会抑制异常(beancurrentlyincreationexception),并且Json组件模块不会注册到对象映射器的消息中解析不可解决的循环引用。

除了在Spring管理之外创建新的对象映射器,我没有找到其他解决方案。

英文:

In general you can use Jackson2ObjectMapperBuilder and build() to generate new object mapper instance with all the recent settings applied. The default ObjectMapper is provided by JackonsObjectMapperConfiguration in JackonAutoConfiguration. All jackson2 object mapper builder instances are created by JacksonObjectMapperBuilderConfiguration in JacksonAutoConfiguration. Details on configuring object mapper can be found here

You can further customize jackson2 object mapper builder using Jackson2ObjectMapperBuilderCustomizerConfiguration if you don't like the standard defaults in JacksonAutoConfiguration

Checkout this answer - it will answer your other questions

Coming to issue - Had the same issue when using jackson2objectbuilder/build or autowiring object mapper for ser/deser annotated with JsonComponent annotation. All these annotations are scanned by JsonComponentModule bean defined in JacksonAutoConfiguration and are registered with default object mapper and all the instances created by jackson2 object mapper builder. So it is not possible to use the not built object mapper and register the ser/deser at the same time. You are right the exception ( beancurrentlyincreationexception ) is suppressed by spring and json component module is not registered with message unresolvale circular reference for object mapper.

I didn't find any solution other than creating a new object mapper outside of spring managed.

huangapple
  • 本文由 发表于 2020年10月8日 18:32:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/64260678.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定