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
类型:主实体,包含三个字段id
和title
标记为非空(!
)
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"));
路由处理流程:
- 监听
/graphql
的POST请求 - 提取请求体中的
query
字段 - 执行GraphQL查询
- 将结果转换为标准GraphQL响应格式
- 序列化为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获取。