如何在 Spring Boot 中为 Camel 配置 Jackson ObjectMapper

How to configure Jackson ObjectMapper for Camel in Spring Boot

我正在尝试使用 Jackson 在 Camel 路由上将 POJO 序列化和反序列化到 JSON。其中一些具有 Java 8 个 LocalDate 字段,我希望将它们序列化为 YYYY-MM-DD 字符串,而不是整数数组。

我们只为 Spring 启动应用程序使用 Java 配置,因此没有 XML Camel 配置。

我已经成功创建了一个 ObjectMapper 来执行我想要的操作,通过将其添加到我们的依赖项中,我们系统的其他部分正在使用它:

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

这是我们的应用程序配置:

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

传出 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()");
    }
}

传入消息路由示例:

@Component
public class MyRouteBuilder extends RouteBuilder {

    @Autowired
    private MyBean myBean;

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

但是,默认情况下,Camel 创建自己的 ObjectMapper 实例,因此不会使用 Jackson2ObjectMapperBuilder 自动添加的 JSR310 serializers/deserializers 或禁用的 WRITE_DATES_AS_TIMESTAMPS 功能。我已阅读 Camel JSON 文档,但它没有显示如何使用 Spring 配置添加自定义数据格式,或如何为所有类型应用全局自定义。

那么我如何告诉 Camel 使用我的 ObjectMapper,只使用 Spring 引导 Java 配置?

在 java 代码中创建 JacksonDataFormat 和 enable/disable 您想要的功能,然后在 Camel 路由中使用该实例。

 .unmarshal(myInstanceGoesHere).

这是使用marshal时的例子,同样可以与unmarshal:

适配器
CamelContext ctx = new DefaultCamelContext();
JacksonDataFormat df = new JacksonDataFormat();

df.setModuleClassNames("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule");

ctx.addRoutes(
    new RouteBuilder() {
      @Override
      public void configure() {
        from("direct:start").marshal(df).log("Out");
      }
    });

如果 Camel 给你带来麻烦,我会恢复直接使用 beans:

  1. 只需创建一个小的 Json 实用程序,它可以进行编组和解组并将您预配置的 ObjectMapper 自动连接到其中。

  2. 利用 Camels 真棒 Spring bean 集成来调用您的实用程序并转换路由中的消息,例如:

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

我通过单步执行 Camel 代码找到了解决方案。因此,虽然它可以满足我的要求,但它可能无法与未来版本的 Camel 一起使用,因为它似乎没有记录并且可能不受支持。

除了问题中的 ObjectMapper bean 之外,我所做的就是将以下 bean 添加到我的 Spring 配置中:

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

重点注意事项:

  • JacksonDataFormat 没有采用没有解组类型的 ObjectMapper 的构造函数。但是,在默认构造函数中,如果未提供解组类型,则会使用 HashMap.class,因此我使用它。神奇的是,这似乎随后习惯于解组所有 POJO 类型。如果您还需要其他 类 的更具体的数据格式,您也需要在其中设置 ObjectMapper
  • Camel 似乎在 bean 注册表中搜索名为 "json-jackson" 的 bean,因此将 Spring bean 设置为使用该名称会诱使 Camel 不创建新的 bean 而是使用我的 bean。
  • bean 范围必须设置为 SCOPE_PROTOTYPE,因为 REST DSL 期望获得 DataFormat 的新实例。参见 CAMEL-7880

使用 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>

并在 CamelContextConfiguration class 中,自动装配 JacksonDataFormat 以配置 class 路径模块的发现和序列化选项的配置:

@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);
    }
}

到目前为止,只有@david-edwards 的建议对我有用。我首先用 id 定义了一个数据格式 bean:"json-jackson"

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

然后格式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);
    }
}

和 CustomEnumSerializer class:

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();
        }
    }
}

我设法使用 org.apache.camel:camel-jackson-starter:2.20.0

非常方便地为 Camel 配置了 ObjectMapper

它公开了一些有用的 ObjectMapper 属性,以便通过 Spring 应用程序属性进行配置。例如,WRITE_DATES_AS_TIMESTAMPS 可以直接从 application.yaml 或 application.properties 文件中设置。

查找 JacksonDataFormatConfiguration class 了解更多详细信息。

我还需要使用一些 Mixins,所以我仍然需要配置 Camel 以使用 Spring 的 ObjectMapper。我最终得到了这个:

配置bean:

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

application.yaml:

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

其中jacksonObjectMapper是配置的Jackson2ObjectMapperBuilder构建的ObjectMapper bean的名称

我无法使用任何示例。有点失望的是,阅读变通方法非常复杂。

在我看来,camel 应该通过使用应用程序附带的相同 Jackson bean 来简化 Spring 默认对象映射器的使用。

我放弃使用 .json() 并将其换成处理器。

如下图,使用了Spring提供的objectMapper。

路线

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

通用处理器 注意这个 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()));
    }

}

如果其他人想知道如何使用版本中的修复程序。 2.17.. 我使用这个 xml 配置让它工作:

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

 </camel:camelContext>

..其中 myObjectMapper 是类型为 ObjectMapper

的 spring bean 的名称

我通过在 pom 中包含 jackson 依赖项解决了问题

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

现在,只需在路由配置中添加 JacksonDataFormat

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

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

各位好消息,Spring Boot 现在支持对象映射器自动发现!只需设置此 属性:

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

If set to true then Jackson will lookup for an objectMapper into the registry

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

日志:

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

(警告只是表示,您在 camel.dataformat.json-jackson.* 下的所有其他属性都将被忽略)

这对我有用 (骆驼 2.2.0)

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

REST 配置

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