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

26

我正在尝试使用Jackson在Camel路由上将POJO序列化和反序列化为JSON。其中一些具有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实例,因此不会自动使用JSR310序列化/反序列化器,也不会使用禁用的WRITE_DATES_AS_TIMESTAMPS功能,这些是由Jackson2ObjectMapperBuilder自动添加的。我已经阅读了Camel JSON文档,但它没有展示如何使用Spring配置添加自定义DataFormat,或者如何对所有类型应用全局自定义。

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


查看jackson的启用/禁用功能:http://camel.apache.org/json.html - Claus Ibsen
请分享您的骆驼路由。 - Fritz Duchardt
@ClausIbsen 我看过了,但它并没有展示如何让Camel意识到自定义的DataFormat。或者如何让它适用于所有POJO而不是特定类。 - David Edwards
@FritzDuchardt 我现在已经在问题中添加了示例路由。 - David Edwards
12个回答

16

大家好,好消息是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.*下的所有其他属性都将被忽略)

更新于2022年8月17日

对于新版本的Camel,自3.15.0以来,请使用不同的属性。

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

2
注意,他们在Camel 3.15中将该属性重命名为camel.dataformat.jackson.auto-discover-object-mapper=true - Lovis
@v.ladynev - 感谢您的编辑!能否说明从哪个版本开始变化,或者至少在哪个Camel版本中适用? - Innokenty
@Innokenty 已完成。感谢您指出。 - v.ladynev

11

我通过阅读Camel代码找到了解决方案。虽然它实现了我的需求,但是由于它似乎没有记录并且可能不受支持,在未来的Camel版本中可能无法使用。

我所做的就是在Spring配置文件中添加以下bean,除了我在问题中的ObjectMapper bean:

@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,使其不创建新的实例并使用我的实例。
  • 由于REST DSL期望获得DataFormat的新实例,因此bean作用域必须设置为SCOPE_PROTOTYPE。请参见CAMEL-7880

到目前为止,这是唯一对我有效的方法。我的解决方案有些变化:不需要objectMapper,我还创建了一个名为“json-jackson”的bean,它是JacksonDataFormat的扩展。 - Georgios Stathis
我也只有一个选项可行(使用Camel 2.17)! - jfneis

6
在Java代码中创建JacksonDataFormat并启用/禁用所需的功能,然后在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");
      }
    });

谢谢。这是一个部分解决方案;我看到了一些不足之处:
  1. 当使用restConfiguration()时,setJsonDataFormat()需要一个字符串,但我没有看到任何方法可以给JacksonDataFormat命名。每个rest()端点都需要添加一个unmarshal()吗?
  2. 为每个路由添加自定义的JacksonDataFormat效率低下,而且可能会被遗忘。我想全局配置一次。
  3. 我已经配置了我想要的ObjectMapper。配置一个新的ObjectMapper似乎效率低下。这不仅仅是禁用功能;我还需要注册JSR310序列化程序/反序列化程序。
- David Edwards
2
我提交了一个工单,以允许配置自定义对象映射器实例用于数据格式:https://issues.apache.org/jira/browse/CAMEL-9275 - Claus Ibsen
1
@ClausIbsen,我看到您在Camel 2.17中修复了那个问题,但我们如何利用这个修复呢?特别是在一个OSGI-blueprint环境中,我们没有使用Spring -- 我们只需通过blueprint(osgi-config.xml)创建一个ObjectMapper类型的bean,它就会自动捕捉到吗? - Michael Lucas
我尝试在JsonDataFormat中使用setModuleClassNames启用JavaTimeModule,但似乎不起作用。不幸的是,这不是启用/禁用功能的问题,所以我看不出这个答案如何解决问题。 - jfneis
1
您能否详细说明一下,以便提供更多的上下文信息?myInstanceGoesHere是什么?它是objectMapper的bean实例吗? - Robbo_UK
显示剩余2条评论

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

3

我通过在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"。 - b15

2

到目前为止,只有@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();
        }
    }
}

2

我成功地使用org.apache.camel:camel-jackson-starter:2.20.0方便地配置了Camel的ObjectMapper。

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

请查找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的名称。


1
这是对我有用的内容(Camel 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")

restConfiguration() .component("restlet") .bindingMode(RestBindingMode.auto) .dataFormatProperty("prettyPrint", "true") .dataFormatProperty("enableFeatures","ACCEPT_CASE_INSENSITIVE_PROPERTIES") 运行得非常好。谢谢 - Burner

1

如果有其他人想知道如何使用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 的名称。


在您提供的camel XML示例中,能否也包含您的<restConfiguration>元素?当我使用"<restConfiguration ... bindingMode="json" jsonDataFormat="json"/>"的restConfiguration时,我收到一个错误消息“因为找不到JSon DataFormat json”。 - JohnC

0
如果在那里使用Camel有困难,我建议直接使用beans。
  1. 简单地创建一个小的Json工具,可以进行编组和解组,并将预配置的ObjectMapper自动装配到其中。

  2. 利用Camel强大的Spring bean集成来调用您的工具并转换路由中的消息,例如:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接