简体   繁体   English

如何在 Spring 引导中为 Camel 配置 Jackson ObjectMapper

[英]How to configure Jackson ObjectMapper for Camel in Spring Boot

I am trying to serialize and deserialize POJOs to and from JSON on Camel routes using Jackson.我正在尝试使用 Jackson 在骆驼路线上序列化和反序列化与 JSON 之间的 POJO。 Some of these have Java 8 LocalDate fields, and I want them to be serialised as YYYY-MM-DD string, not as an array of integers.其中一些有 Java 8 LocalDate 字段,我希望它们被序列化为 YYYY-MM-DD 字符串,而不是整数数组。

We only use Java configuration for our Spring Boot application, so no XML Camel configuration.我们仅将 Java 配置用于我们的 Spring 引导应用程序,因此没有 XML 骆驼配置。

I have successfully created an ObjectMapper that does what I want, which is being used by other parts of our system by adding this to our dependencies:我已经成功地创建了一个 ObjectMapper 来做我想做的事,通过将它添加到我们的依赖项中,我们系统的其他部分正在使用它:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

and this to our application configuration:这对我们的应用程序配置:

@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
    return builder
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .build();
}

Example outgoing REST route:传出 REST 路由示例:

@Component
public class MyRouteBuilder extends RouteBuilder {

    @Override
    public void configure() throws Exception {

        restConfiguration().component("servlet").contextPath("/mycontext")
                .port(8080).bindingMode(RestBindingMode.json);

        rest("/myendpoint)
                .get()
                .route()
                .to("bean:myService?method=myMethod()");
    }
}

Example incoming message route:传入消息路由示例:

@Component
public class MyRouteBuilder extends RouteBuilder {

    @Autowired
    private MyBean myBean;

    @Override
    public void configure() {
        from(uri)
                .unmarshal().json(JsonLibrary.Jackson)
                .bean(myBean);
    }
}

However, by default Camel creates its own ObjectMapper instances so does not pick up on either the JSR310 serializers/deserializers that Jackson2ObjectMapperBuilder adds automatically, or the disabled WRITE_DATES_AS_TIMESTAMPS feature.但是,默认情况下,Camel 创建自己的 ObjectMapper 实例,因此不会使用Jackson2ObjectMapperBuilder自动添加的 JSR310 序列化器/反序列化器或禁用的WRITE_DATES_AS_TIMESTAMPS功能。 I have read the Camel JSON documentation, but it does not show how to add a custom DataFormat using Spring configuration, or how to apply a global customisation for all types.我已阅读Camel JSON文档,但它没有显示如何使用 Spring 配置添加自定义数据格式,或如何为所有类型应用全局自定义。

So how can I tell Camel to use my ObjectMapper, using only Spring Boot Java configuration?那么如何告诉 Camel 使用我的 ObjectMapper,仅使用 Spring 引导 Java 配置?

I have found a solution by stepping through the Camel code.我通过单步执行 Camel 代码找到了解决方案。 So while it does what I want, it might not work with future versions of Camel since it appears to be undocumented and potentially unsupported.因此,虽然它可以满足我的要求,但它可能不适用于 Camel 的未来版本,因为它似乎没有记录并且可能不受支持。

All I do is add the following bean to my Spring config, in additional to my ObjectMapper bean in the question:我所做的就是将以下 bean 添加到我的 Spring 配置中,以及问题中的ObjectMapper bean:

@Bean(name = "json-jackson")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public JacksonDataFormat jacksonDataFormat(ObjectMapper objectMapper) {
    return new JacksonDataFormat(objectMapper, HashMap.class);
}

The crucial points to note:需要注意的关键点:

  • There is no constructor for JacksonDataFormat that takes an ObjectMapper without an unmarshal type. JacksonDataFormat没有采用没有解组类型的ObjectMapper构造函数。 However, in the default constructor a HashMap.class is used when no unmarshal type is provided, so I use that.但是,在默认构造函数中,当未提供解组类型时使用HashMap.class ,所以我使用它。 By some magic, this appears to then get used to unmarshal all POJO types.通过某种魔法,这似乎习惯于解组所有 POJO 类型。 If you also need more specific data formats for other classes, you will need to set the ObjectMapper in them too.如果您还需要其他类的更具体的数据格式,您也需要在其中设置ObjectMapper
  • Camel appears to search the bean registry for a bean called "json-jackson", so setting the Spring bean to use that name tricks Camel into not creating a new one and using mine instead. Camel 似乎会在 bean 注册表中搜索一个名为“json-jackson”的 bean,因此将 Spring bean 设置为使用该名称会使 Camel 不创建新的 bean 而是使用我的。
  • The bean scope must be set to SCOPE_PROTOTYPE because the REST DSL expects to get a new instance of the DataFormat . bean 范围必须设置为SCOPE_PROTOTYPE因为 REST DSL 期望获得DataFormat的新实例。 See CAMEL-7880 .参见CAMEL-7880

在 Java 代码中创建JacksonDataFormat并启用/禁用您想要的功能,然后在 Camel 路由中使用该实例。

 .unmarshal(myInstanceGoesHere).

Using Spring and Camel 2.18.1, I was able to achieve the same by adding the following dependencies:使用 Spring 和 Camel 2.18.1,我能够通过添加以下依赖项来实现相同的目标:

<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.6.1</version>
</dependency>

and in a CamelContextConfiguration class, autowiring the JacksonDataFormat in order to configure the discovery of classpath modules and the configuration of the serialization options:CamelContextConfiguration类中,自动JacksonDataFormat以配置类路径模块的发现和序列化选项的配置:

@Configuration
public class CamelContextConfig implements CamelContextConfiguration {

    @Autowired
    public JacksonDataFormat jacksonDataFormat;

    @Override
    public void beforeApplicationStart(CamelContext camelContext) {
    }

    @Override
    public void afterApplicationStart(CamelContext camelContext) {
        jacksonDataFormat
            .getObjectMapper()
            .findAndRegisterModules()
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }
}

Good news everyone, object mapper autodiscovery is supported now for Spring Boot!大家好消息,Spring Boot 现在支持对象映射器自动发现! Simply set this property:只需设置此属性:

camel.dataformat.json-jackson.auto-discover-object-mapper=true

If set to true then Jackson will lookup for an objectMapper into the registry如果设置为 true,那么 Jackson 将在注册表中查找 objectMapper

Docs: https://camel.apache.org/components/latest/dataformats/json-jackson-dataformat.html#_spring_boot_auto_configuration文档: https : //camel.apache.org/components/latest/dataformats/json-jackson-dataformat.html#_spring_boot_auto_configuration

Logs:日志:

INFO o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.3.0 (CamelContext: camel-1) is starting
INFO o.a.c.c.jackson.JacksonDataFormat        : Found single ObjectMapper in Registry to use: com.fasterxml.jackson.databind.ObjectMapper@20a1b3ae
WARN o.a.c.c.jackson.JacksonDataFormat        : The objectMapper was already found in the registry, no customizations will be applied

(the warning just denotes, that all your other properties under camel.dataformat.json-jackson.* are ignored) (警告只是表示, camel.dataformat.json-jackson.*下的所有其他属性都被忽略)

So far only the suggestion of @david-edwards has worked for me.到目前为止,只有@david-edwards 的建议对我有用。 I first defined a data format bean with the id: "json-jackson"我首先定义了一个id为“json-jackson”的数据格式bean

<bean id="json-jackson" class="com.mydomain.JacksonDataFormatExt" />

Then the format class:然后是格式类:

public class JacksonDataFormatExt extends JacksonDataFormat{

    public JacksonDataFormatExt(){
        super();
        setPrettyPrint(true);
        setEnableFeatures(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS.name());
        SimpleModule s = new SimpleModule();
        s.addSerializer(CustomEnum.class, new CustomEnumSerializer());
        addModule(s);
    }
}

And the CustomEnumSerializer class:和 CustomEnumSerializer 类:

public class CustomEnumSerializer extends JsonSerializer<CustomEnum> {

    @Override
    public void serialize(CustomEnumvalue, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        String stringValue = value.getNlsText();
        if(stringValue != null && !stringValue.isEmpty() && !stringValue.equals("null")) {
            jgen.writeString(stringValue);
        } else {
            jgen.writeNull();
        }
    }
}

I managed to configure ObjectMapper for Camel quite conveniently using org.apache.camel:camel-jackson-starter:2.20.0我设法使用org.apache.camel:camel-jackson-starter:2.20.0非常方便地为 Camel 配置了 ObjectMapper

It exposes some of the useful ObjectMapper properties for configuration via Spring application properties.它通过 Spring 应用程序属性公开了一些有用的 ObjectMapper 属性以进行配置。 WRITE_DATES_AS_TIMESTAMPS for example can be set straight from application.yaml or application.properties file.例如,可以直接从 application.yaml 或 application.properties 文件设置 WRITE_DATES_AS_TIMESTAMPS。

Look for JacksonDataFormatConfiguration class for more details.查找 JacksonDataFormatConfiguration 类以获取更多详细信息。

I also needed to use some Mixins so I still needed to configure Camel to use a Spring's ObjectMapper.我还需要使用一些 Mixin,所以我仍然需要配置 Camel 以使用 Spring 的 ObjectMapper。 I ended up with this:我结束了这个:

Configuration bean:配置豆:

@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
    return new Jackson2ObjectMapperBuilderCustomizer() {
        @Override
        public void customize(Jackson2ObjectMapperBuilder builder) {
            builder.mixIn(Person.class, PersonMixin.class);
        }
    }
}

application.yaml:应用程序.yaml:

camel:
  dataformat:
    json-jackson:
      disable-features: WRITE_DATES_AS_TIMESTAMPS
      object-mapper: jacksonObjectMapper

Where jacksonObjectMapper is the name of the ObjectMapper bean built by the configured Jackson2ObjectMapperBuilder其中jacksonObjectMapper是配置的 Jackson2ObjectMapperBuilder 构建的 ObjectMapper bean 的名称

If anyone else was wondering how to use the fix put in the ver.如果其他人想知道如何使用版本中的修复程序。 2.17.. I got it working using this xml configuration: 2.17 .. 我使用这个 xml 配置让它工作:

 <camel:camelContext id="defaultCamelContext">
       .....
        <camel:dataFormats>
            <camel:json id="json" library="Jackson"  objectMapper="myObjectMapper"/>
        </camel:dataFormats>

 </camel:camelContext>

..where myObjectMapper is a name of a spring bean of type ObjectMapper ..其中 myObjectMapper 是 ObjectMapper 类型的 spring bean 的名称

I solved by including jackson dependency in pom我通过在 pom 中包含 jackson 依赖来解决

<dependency>
  <groupId>org.apache.camel</groupId>
  <artifactId>camel-jackson-starter</artifactId>
  <version>${camel.version}</version>
</dependency>

Now, simply adding JacksonDataFormat in route configuration现在,只需在路由配置中添加 JacksonDataFormat

public void configure() throws Exception {
    JacksonDataFormat jsonDf = new JacksonDataFormat(Card.class);
    jsonDf.setPrettyPrint(true);

    from("direct:endpoint")
    .marshal(jsonDf)
    .convertBodyTo(String.class)
    .....
}

If Camel gives you trouble there, I would revert to using beans directly:如果 Camel 在那里给你带来麻烦,我会恢复直接使用 bean:

  1. Simply create a small Json utility that can do marshalling and unmarshalling and autowire your preconfigured ObjectMapper into it.只需创建一个小型 Json 实用程序,它可以进行编组和解组并将预配置的 ObjectMapper 自动装配到其中。

  2. Harness Camels awesome Spring bean integration to call your utility and transform the Message in the route, eg:利用 Camels 很棒的 Spring bean 集成来调用您的实用程序并转换路由中的消息,例如:

     from(uri) .unmarshal().json(JsonLibrary.Jackson) .beanRef("jsonUtil", "unmarshal") .bean(myBean);

I could not get any of the examples to work.我无法让任何示例工作。 A little disappointed that this is quite complicated from reading workarounds.有点失望的是,阅读变通方法非常复杂。

In my opinion camel should make it easy to use the Spring default object mapper by using the same Jackson bean that comes with the application.在我看来,camel 应该通过使用应用程序附带的相同 Jackson bean 来轻松使用 Spring 默认对象映射器。

I forgone the use of .json() and swapped it for a processor.我放弃使用.json()并将其换成处理器。

like the following, this used the objectMapper provided by Spring.如下所示,这里使用了 Spring 提供的 objectMapper。

Route路线

from(CONSUME_TAG)
 .process("jsonProcessor")
 .to("direct:anotherRoute")
 .end();

Generic Processor notice how this Autowires the spring boot objectMapper bean.通用处理器注意这如何自动装配 spring boot objectMapper bean。

@Component
public class JsonProcessor implements Processor {

    @Autowired
    ObjectMapper objectMapper;

    @Override
    public void process(Exchange exchange) throws Exception {
        exchange.getOut().setBody(objectMapper.writeValueAsString(exchange.getIn().getBody()));
    }

}

Here is what works for me (Camel 2.2.0)这对我有用(Camel 2.2.0)

    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
        <version>2.12.5</version>
    </dependency>

REST configuration休息配置

            restConfiguration().dataFormatProperty("moduleClassNames", "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule")
                               .dataFormatProperty("disableFeatures", "WRITE_DATES_AS_TIMESTAMPS")

Spring Boot configures ObjectMapper with required jackson-datatype-jsr310 module during start up and puts it to the Spring Context. Spring Boot 在启动期间使用所需jackson-datatype-jsr310模块配置ObjectMapper ,并将其放入 Spring 上下文。

New versions of Camel don't use ObjectMapper form Spring Context by default and create own ObjectMapper without jackson-datatype-jsr310 . Camel 的新版本默认不使用ObjectMapper形式 Spring Context 并在没有jackson-datatype-jsr310的情况下创建自己的ObjectMapper

  1. For new versions of Camel add this to the application.yaml对于骆驼的新版本,将此添加到application.yaml中。yaml
camel.dataformat.jackson.auto-discover-object-mapper: true
  1. Use to unmarshall用于解组
from("some").unmarshal().json(SomeClass.class)

And don't use并且不要使用

from("some").unmarshal(new JacksonDataFormat(SomeClass.class))

It will not get ObjectMapper from Spring Context.它不会从 Spring 上下文中获取ObjectMapper

To check what happens debug AbstractJacksonDataFormat.doStart() method.要检查会发生什么调试AbstractJacksonDataFormat.doStart()方法。 It is called during Camel start up and gets ObjectMapper from the Spring Context or creates a new one.它在 Camel 启动期间被调用并从 Spring 上下文中获取ObjectMapper或创建一个新的。 When new ObjectMapper is created, it doesn't have required Jackson jackson-datatype-jsr310 module and an error happens.创建新的ObjectMapper时,它不需要 Jackson jackson-datatype-jsr310模块并发生错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM