1. 简介

本教程将使用Apache Camel构建一个小型应用,同时暴露GraphQL和REST接口。Apache Camel是一个强大的集成框架,能简化连接不同系统(包括API、数据库和消息服务)的过程。

2. 项目搭建

首先在pom.xml中添加以下依赖:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-core</artifactId>
    <version>4.11.0</version>
</dependency>
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java</artifactId>
    <version>23.1</version>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-jetty</artifactId>
    <version>4.11.0</version>
</dependency>
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-graphql</artifactId>
    <version>4.11.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.18.0</version>
</dependency>

这些依赖提供了构建应用所需的核心组件:

  • camel-jetty:使用Jetty作为嵌入式Web服务器暴露HTTP接口
  • camel-graphql:用于处理GraphQL查询
  • ✅ Jackson:处理JSON序列化/反序列化

3. 创建Book模型

创建一个简单的Book模型类:

public class Book {
    private String id;
    private String title;
    private String author;

    // 构造方法、getter和setter方法
}

4. 创建服务类

创建BookService类模拟数据服务:

public class BookService {
    private final List<Book> books = new ArrayList<>();

    public BookService() {
        books.add(new Book("1", "Clean Code", "Robert"));
        books.add(new Book("2", "Effective Java", "Joshua"));
    }

    public List<Book> getBooks() {
        return books;
    }

    public Book getBookById(String id) {
        return books.stream().filter(b -> b.getId().equals(id)).findFirst().orElse(null);
    }

    public Book addBook(Book book) {
        books.add(book);
        return book;
    }
}

该服务提供三个核心操作:

  • 获取所有书籍
  • 根据ID获取书籍
  • 添加新书籍

5. 使用Camel创建REST接口

通过Apache Camel + Jetty暴露RESTful接口。在BookRoute类中定义路由:

public class BookRoute extends RouteBuilder {
    private final BookService bookService = new BookService();

    @Override
    public void configure() {
        onException(Exception.class)
          .handled(true)
          .setHeader("Content-Type", constant("application/json"))
          .setBody(simple("{\"error\": \"${exception.message}\"}"));
          
        restConfiguration()
          .component("jetty")
          .host("localhost")
          .port(8080)
          .contextPath("/api")
          .bindingMode(RestBindingMode.json);

        //...
    }
}

关键配置说明:

  • ⚠️ onException():创建全局异常处理器,捕获所有未处理异常
  • restConfiguration():定义服务器设置
    • 使用Jetty监听8080端口
    • 设置JSON绑定模式(自动转换Java对象为JSON响应)
    • 基础URL前缀为/api

接下来定义三个REST接口:

  • GET /api/books:获取所有书籍
  • GET /api/books/{id}:根据ID获取书籍
  • POST /api/books:添加新书籍
rest("/books") 
  .get().to("direct:getAllBooks") 
  .get("/{id}").to("direct:getBookById")
  .post().type(Book.class).to("direct:addBook");

实现内部路由逻辑:

from("direct:getAllBooks")
  .bean(bookService, "getBooks");

from("direct:getBookById")
  .bean(bookService, "getBookById(${header.id})");

from("direct:addBook")
  .bean(bookService, "addBook");

通过Camel的流式DSL,我们实现了:

  • ✅ HTTP路由与业务逻辑的清晰分离
  • ✅ 可维护且可扩展的REST API暴露方式

6. 创建GraphQL Schema

books.graphqls文件中定义GraphQL Schema:

type Book {
  id: String!
  title: String!
  author: String
}

type Query {
  books: [Book]
  bookById(id: String!): Book
}

type Mutation {
  addBook(id: String!, title: String!, author: String): Book
}

Schema结构说明:

  • Book类型:主实体,包含三个字段
    • idtitle标记为非空(!
  • Query类型:定义查询操作
    • books:获取所有书籍
    • bookById:根据ID获取书籍
  • Mutation类型:定义修改操作
    • addBook:添加新书籍(title必填,author可选)

创建CustomSchemaLoader连接GraphQL与Java服务:

public class CustomSchemaLoader{
    private final BookService bookService = new BookService();

    public GraphQLSchema loadSchema() {
        try (InputStream schemaStream = getClass().getClassLoader().getResourceAsStream("books.graphqls")) {
            if (schemaStream == null) {
                throw new RuntimeException("GraphQL schema file 'books.graphqls' not found in classpath");
            }

            TypeDefinitionRegistry registry = new SchemaParser()
              .parse(new InputStreamReader(schemaStream));

            RuntimeWiring wiring = buildRuntimeWiring();
            return new SchemaGenerator().makeExecutableSchema(registry, wiring);

        } catch (Exception e) {
            logger.error("Failed to load GraphQL schema", e);
            throw new RuntimeException("GraphQL schema initialization failed", e);
        }
    }

    public RuntimeWiring buildRuntimeWiring() {
        return RuntimeWiring.newRuntimeWiring()
          .type("Query", builder -> builder
            .dataFetcher("books", env -> bookService.getBooks())
            .dataFetcher("bookById", env -> bookService.getBookById(env.getArgument("id"))))
          .type("Mutation", builder -> builder
            .dataFetcher("addBook", env -> {
                String id = env.getArgument("id");
                String title = env.getArgument("title");
                String author = env.getArgument("author");
                if (title == null || title.isEmpty()) {
                    throw new IllegalArgumentException("Title cannot be empty");
                }
                return bookService.addBook(new Book(id, title, author));
          }))
          .build();
    }
}

关键点:

  • dataFetcher():连接GraphQL操作与Java服务方法
  • 例如books查询 → bookService.getBooks()
  • addBook mutation → 提取参数并调用bookService.addBook()

7. 添加GraphQL路由

使用Camel暴露GraphQL接口:

from("jetty:http://localhost:8088/graphql?matchOnUriPrefix=true")
    .log("Received GraphQL request: ${body}")
    .convertBodyTo(String.class)
    .process(exchange -> {
        String body = exchange.getIn().getBody(String.class);
        try {
            Map<String, Object> payload = new ObjectMapper().readValue(body, Map.class);
            String query = (String) payload.get("query");
            if (query == null || query.trim().isEmpty()) {
                throw new IllegalArgumentException("Missing 'query' field in request body");
            }
            ExecutionInput executionInput = ExecutionInput.newExecutionInput()
              .query(query)
              .build();
            ExecutionResult result = graphQL.execute(executionInput);
            Map<String, Object> response = result.toSpecification();
            exchange.getIn().setBody(response);
        } catch (Exception e) {
            throw new RuntimeException("GraphQL processing error", e);
        }
    })
    .marshal().json(JsonLibrary.Jackson)
    .setHeader(Exchange.CONTENT_TYPE, constant("application/json"));

路由处理流程:

  1. 监听/graphql的POST请求
  2. 提取请求体中的query字段
  3. 执行GraphQL查询
  4. 将结果转换为标准GraphQL响应格式
  5. 序列化为JSON返回

8. 主应用类

创建CamelRestGraphQLApp启动应用:

public class CamelRestGraphQLApp {
    public static void main(String[] args) throws Exception {
        CamelContext context = new DefaultCamelContext();
        context.addRoutes(new BookRoute());
        context.start();
        logger.info("Server running at http://localhost:8080");
        Thread.sleep(Long.MAX_VALUE);
        context.stop();
    }
}

关键说明:

  • 初始化Camel上下文
  • 注册路由
  • Thread.sleep(Long.MAX_VALUE):保持应用运行(生产环境应替换为更健壮的生命周期管理)

9. 测试

使用Camel的ProducerTemplate编写测试:

@Test
void whenCallingRestGetAllBooks_thenShouldReturnBookList() {
    String response = template.requestBodyAndHeader(
      "http://localhost:8080/api/books",
      null,
      Exchange.CONTENT_TYPE,
      "application/json",
      String.class
    );

    assertNotNull(response);
    assertTrue(response.contains("Clean Code"));
    assertTrue(response.contains("Effective Java"));
}

@Test
void whenCallingBooksQuery_thenShouldReturnAllBooks() {
    String query = """
    {
        "query": "{ books { id title author } }"
    }""";

    String response = template.requestBodyAndHeader(
      "http://localhost:8080/graphql",
      query,
      Exchange.CONTENT_TYPE,
      "application/json",
      String.class
    );
    assertNotNull(response);
    assertTrue(response.contains("Clean Code"));
    assertTrue(response.contains("Effective Java"));
}

测试覆盖:

  • REST接口:获取所有书籍
  • GraphQL接口:执行books查询

10. 总结

本文成功在Camel应用中集成了REST和GraphQL接口,实现了通过查询和变更API高效管理书籍数据。

完整源码可在GitHub获取。


原始标题:How to Configure GraphQL/REST APIs Using Apache Camel | Baeldung