1. 概述

Apache Camel 是一个强大的开源集成框架,支持多种已知的 企业集成模式(Enterprise Integration Patterns)

在使用 Camel 进行消息路由时,通常我们会使用它提供的可插拔 数据格式(data formats)。由于 JSON 在现代 API 和数据服务中非常流行,因此它自然也成为了常用选择。

在本教程中,我们将介绍两种方式,使用 camel-jackson 组件将 JSON 数组 反序列化为 Java 对象列表

2. 依赖配置

首先,将 camel-jackson-starter 添加到你的 pom.xml 中:

<dependency>
    <groupId>org.apache.camel.springboot</groupId>
    <artifactId>camel-jackson-starter</artifactId>
    <version>3.21.0</version>
</dependency>

然后,为了支持单元测试,添加 camel-test-spring-junit5

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-test-spring-junit5</artifactId>
    <version>3.21.0</version>
    <scope>test</scope>
</dependency>

3. Fruit 领域类

本教程中,我们使用一些简单的 POJO 来建模水果领域。

先定义一个包含 idnameFruit 类:

public class Fruit {

    private String name;
    private int id;

    // 标准 getter 和 setter
}

再定义一个容器类 FruitList 来包装 Fruit 列表:

public class FruitList {

    private List<Fruit> fruits;

    public List<Fruit> getFruits() {
        return fruits;
    }

    public void setFruits(List<Fruit> fruits) {
        this.fruits = fruits;
    }
}

在接下来的章节中,我们将演示如何将一个表示水果列表的 JSON 字符串反序列化为这些领域类最终目标是得到一个 List<Fruit> 类型的变量,便于后续处理

4. 反序列化 JSON FruitList

在这个例子中,我们使用如下 JSON 格式来表示一个水果列表:

{
    "fruits": [
        {
            "id": 100,
            "name": "Banana"
        },
        {
            "id": 101,
            "name": "Apple"
        }
    ]
}

⚠️ 注意:这个 JSON 表示的是一个包含名为 fruits 属性的对象,该属性的值才是我们真正要的数组

接下来我们配置 Apache Camel 的 路由(route) 来执行反序列化操作:

@Bean
RoutesBuilder route() {
    return new RouteBuilder() {
        @Override
        public void configure() throws Exception {
            from("direct:jsonInput").unmarshal(new JacksonDataFormat(FruitList.class))
              .to("mock:marshalledObject");
        }
    };
}

解释一下:

  • 使用 direct 接口,命名为 jsonInput
  • 调用 unmarshal 方法,使用 JacksonDataFormat 并指定目标类型为 FruitList.class
  • 最终将结果发送到 mock:marshalledObject 接口用于测试

JacksonDataFormat 是对 Jackson 的 ObjectMapper 的简单封装,支持 JSON 与 Java 对象之间的序列化和反序列化

下面是我们为该路由编写的第一个单元测试:

@CamelSpringBootTest
@SpringBootTest
public class FruitListJacksonUnmarshalUnitTest {

    @Autowired
    private ProducerTemplate template;

    @EndpointInject("mock:marshalledObject")
    private MockEndpoint mock;

    @Test
    public void givenJsonFruitList_whenUnmarshalled_thenSuccess() throws Exception {
        mock.setExpectedMessageCount(1);
        mock.message(0).body().isInstanceOf(FruitList.class);

        String json = readJsonFromFile("/json/fruit-list.json");
        template.sendBody("direct:jsonInput", json);
        mock.assertIsSatisfied();

        FruitList fruitList = mock.getReceivedExchanges().get(0).getIn().getBody(FruitList.class);
        assertNotNull("Fruit lists should not be null", fruitList);

        List<Fruit> fruits = fruitList.getFruits();
        assertEquals("There should be two fruits", 2, fruits.size());

        Fruit fruit = fruits.get(0);
        assertEquals("Fruit name", "Banana", fruit.getName());
        assertEquals("Fruit id", 100, fruit.getId());

        fruit = fruits.get(1);
        assertEquals("Fruit name", "Apple", fruit.getName());
        assertEquals("Fruit id", 101, fruit.getId());
    }
}

测试的关键点包括:

  • 使用 @CamelSpringBootTest 注解启动 Camel 测试环境
  • 通过 @EndpointInject 注入 mock 接口
  • 设置期望:收到一条消息,且消息体是 FruitList 类型
  • 使用 ProducerTemplate 将 JSON 字符串发送到 direct:jsonInput
  • 验证结果并断言内容正确

✅ 测试通过说明路由配置正确,JSON 被成功反序列化!

5. 反序列化 JSON Fruit 数组

有时候我们可能不想使用容器类,而是直接处理 JSON 数组:

[
    {
        "id": 100,
        "name": "Banana"
    },
    {
        "id": 101,
        "name": "Apple"
    }
]

对应的 Camel 路由配置几乎一样,只是换成了 ListJacksonDataFormat

@Bean
RoutesBuilder route() {
    return new RouteBuilder() {
        @Override
        public void configure() throws Exception {
            from("direct:jsonInput").unmarshal(new ListJacksonDataFormat(Fruit.class))
              .to("mock:marshalledObject");
        }
    };
}

ListJacksonDataFormat 是专门用于处理 JSON 数组的 Jackson 数据格式类

单元测试也很类似:

@Test
public void givenJsonFruitArray_whenUnmarshalled_thenSuccess() throws Exception {
    mock.setExpectedMessageCount(1);
    mock.message(0).body().isInstanceOf(List.class);

    String json = readJsonFromFile("/json/fruit-array.json");
    template.sendBody("direct:jsonInput", json);
    mock.assertIsSatisfied();

    @SuppressWarnings("unchecked")
    List<Fruit> fruitList = mock.getReceivedExchanges().get(0).getIn().getBody(List.class);
    assertNotNull("Fruit lists should not be null", fruitList);

    assertEquals("There should be two fruits", 2, fruitList.size());

    Fruit fruit = fruitList.get(0);
    assertEquals("Fruit name", "Banana", fruit.getName());
    assertEquals("Fruit id", 100, fruit.getId());

    fruit = fruitList.get(1);
    assertEquals("Fruit name", "Apple", fruit.getName());
    assertEquals("Fruit id", 101, fruit.getId());
}

⚠️ 与前一个测试相比,有两点小变化:

  • 期望消息体是 List.class
  • 由于泛型擦除,获取 List<Fruit> 时会提示类型不安全,需要使用 @SuppressWarnings("unchecked")

6. 总结

在本教程中,我们展示了使用 Camel 消息路由和 camel-jackson 组件反序列化 JSON 数组的两种方式:

  • JacksonDataFormat:用于将 JSON 反序列化为一个对象(如 FruitList
  • ListJacksonDataFormat:用于将 JSON 数组直接反序列化为 List<Fruit> 类型

两者主要区别在于目标类型是对象还是列表。

📌 本文完整代码可从 GitHub 获取。


原始标题:Unmarshalling a JSON Array Using camel-jackson