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

Posted

技术标签:

【中文标题】如何在 Spring Boot 中为 Camel 配置 Jackson ObjectMapper【英文标题】:How to configure Jackson ObjectMapper for Camel in Spring Boot 【发布时间】:2016-01-28 14:22:19 【问题描述】:

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

我们的 Spring Boot 应用程序只使用 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 序列化器/反序列化器或禁用的 WRITE_DATES_AS_TIMESTAMPS 功能。我已阅读 Camel JSON 文档,但它没有显示如何使用 Spring 配置添加自定义 DataFormat,或如何为所有类型应用全局自定义。

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

【问题讨论】:

查看jackson的启用/禁用功能:camel.apache.org/json.html 请分享你的骆驼路线。 @ClausIbsen 我读到了,但它没有显示如何让 Camel 了解自定义 DataFormat。或者如何使它适用于所有 POJO 而不是特定的类。 @FritzDuchardt 我现在已经为问题添加了示例路线。 【参考方案1】:

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

 .unmarshal(myInstanceGoesHere).

【讨论】:

谢谢。这是部分解决方案;我看到了一些不足之处: 1. 使用 restConfiguration() 时,setJsonDataFormat() 需要一个字符串,但我看不到给 JacksonDataFormat 一个名称。每个 rest() 端点都需要添加一个 unmarshal() 吗? 2. 为每条路由添加自定义 JacksonDataFormat 效率低下,可能会被遗忘。我想全局配置一次。 3. 我已经按照我想要的方式配置了一个 ObjectMapper。必须配置一个新的似乎效率低下。这不仅仅是禁用功能;我也需要注册 JSR310 序列化器/反序列化器。 我记录了一张票以允许配置自定义对象映射器实例以用于数据格式:issues.apache.org/jira/browse/CAMEL-9275 谢谢,克劳斯。在我看来,如果有一个现有的 ObjectMapper bean,它应该自动使用,如果不存在,则只创建一个新的 ObjectMapper。 @ClausIbsen 我看到您在 Camel 2.17 中修复了该票证,但我们如何利用该修复程序?尤其是在我们不使用 Spring 的 OSGI 蓝图环境中——我们是否只是通过 ObjectMapper 类型的蓝图 (osgi-config.xml) 创建一个 bean,它会自动获取它? 我尝试在 JsonDataFormat 中使用 setModuleClassNames 启用 JavaTimeModule,但似乎不起作用。不幸的是,这不是启用/禁用功能的问题,所以我看不出这个答案如何解决问题。【参考方案2】:

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

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

    利用 Camels 出色的 Spring bean 集成来调用您的实用程序并在路由中转换消息,例如:

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

【讨论】:

【参考方案3】:

我通过单步执行 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 范围必须设置为SCOPE_PROTOTYPE,因为 REST DSL 期望获得DataFormat 的新实例。见CAMEL-7880。

【讨论】:

到目前为止,这是唯一对我有用的东西。我的解决方案的一些变化:不需要 objectMapper,我还创建了一个名称为“json-jackson”的 bean,它是 JacksonDataFormat 的扩展。 唯一对我有用的选项(使用 Camel 2.17)!【参考方案4】:

使用 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 类中,自动装配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);
    

【讨论】:

【参考方案5】:

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

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

然后是格式类:

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 类:

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

【讨论】:

【参考方案6】:

我设法使用 org.apache.camel:camel-jackson-starter:2.20.0 非常方便地为 Camel 配置 ObjectMapper

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

查找 JacksonDataFormatConfiguration 类以获取更多详细信息。

我还需要使用一些 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的名称

【讨论】:

【参考方案7】:

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

在我看来,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()));
    


【讨论】:

【参考方案8】:

如果其他人想知道如何使用版本中的修复。 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 的名称

【讨论】:

在您包含的骆驼 XML 示例中,您是否也可以包含您的 元素?当我使用“”的restConfiguration 时,我收到一条错误消息“因为找不到JSon DataFormat json”【参考方案9】:

我通过在 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)
    .....

【讨论】:

这看起来是目前最好的答案。 JSON Jackson 组件文档还提到,如果您需要自定义 ObjectMapper 超出此范围,您可以创建一个 ObjectMapper bean。 "如果你在注册表中设置了一个 ObjectMapper,那么 Camel 会自动查找并使用这个 ObjectMapper"【参考方案10】:

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

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

如果设置为 true,Jackson 将在注册表中查找 objectMapper

文档: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.* 下的所有其他属性都将被忽略)

【讨论】:

【参考方案11】:

这对我有用 (骆驼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")

【讨论】:

以上是关于如何在 Spring Boot 中为 Camel 配置 Jackson ObjectMapper的主要内容,如果未能解决你的问题,请参考以下文章

Camel ActiveMQ + Spring boot 不读取 spring activemq 配置

Apache Camel 与 Spring Boot 集成,通过FTP定时采集处理文件

spring boot + apache camel + mongodb 集成问题

Spring Boot + Apache Camel + Freemarker 自定义模板加载器

在 Spring Boot Camel 应用程序公开的 Micrometer / Prometheus 信息中包含其他 JMX 指标

从 Camel 和 Spring Boot 到 MS SQL Server 的 JDBC