2. GraphQL 概述

GraphQL 是一种用于构建 Web 服务的新兴技术,作为 REST 的替代方案。最近涌现出多个 Java 库用于创建和调用 GraphQL 服务。

2.1. GraphQL 模式

GraphQL 服务器通过一组类型定义服务能力,这些类型描述了可通过服务查询的可能数据集。虽然服务可用任何语言编写,但模式必须使用 GraphQL 模式语言定义。

示例模式定义了两个类型(Book 和 Author)及一个查询操作(allBooks):

type Book {
    title: String!
    author: Author
}

type Author {
    name: String!
    surname: String!
}

type Query {
    allBooks: [Book]
}

schema {
    query: Query
}

关键点

  • Query 类型特殊,定义了 GraphQL 查询的入口点
  • 感叹号(!)表示字段不可为空

2.2. 查询与变更

GraphQL 的核心是请求对象上的特定字段。示例查询获取所有书籍标题:

{
    "allBooks" {
        "title"
    }
}

服务器返回 JSON 格式响应:

{
    "data": {
        "allBooks": [
            {
                "title": "Title 1"
            },
            {
                "title": "Title 2"
            }
        ]
    }
}

注意:变更(Mutation)用于修改操作,本文聚焦查询。

3. GraphQL 服务器实现

使用 GraphQL Java 库创建简单服务器:

3.1. 查询解析器实现

public class GraphQLQuery implements GraphQLQueryResolver {

    private BookRepository repository;

    public GraphQLQuery(BookRepository repository) {
        this.repository = repository;
    }

    public List<Book> allBooks() {
        return repository.getAllBooks();
    }
}

3.2. Servlet 端点暴露

@WebServlet(urlPatterns = "/graphql")
public class GraphQLEndpoint extends HttpServlet {

    private SimpleGraphQLHttpServlet graphQLServlet;

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
      throws ServletException, IOException {
        graphQLServlet.service(req, resp);
    }

    @Override
    public void init() {
        GraphQLSchema schema = SchemaParser.newParser()
          .resolvers(new GraphQLQuery(new BookRepository()))
          .file("schema.graphqls")
          .build()
          .makeExecutableSchema();
        graphQLServlet = SimpleGraphQLHttpServlet
          .newBuilder(schema)
          .build();
    }
}

3.3. 启动与测试

使用 Maven 插件运行:

mvn jetty:run

测试接口:

http://localhost:8080/graphql?query={allBooks{title}}

4. HTTP 客户端调用

GraphQL 服务通过 HTTP 暴露,可用任何 Java HTTP 客户端调用。

4.1. 发送请求

使用 Apache HttpClient 示例:

public static HttpResponse callGraphQLService(String url, String query) 
  throws URISyntaxException, IOException {
    HttpClient client = HttpClientBuilder.create().build();
    HttpGet request = new HttpGet(url);
    URI uri = new URIBuilder(request.getURI())
      .addParameter("query", query)
      .build();
    request.setURI(uri);
    return client.execute(request);
}

替代方案:OkHttp、Java 11+ HttpClient 等均可使用

4.2. 解析响应

使用 Jackson 解析 JSON 响应:

HttpResponse httpResponse = callGraphQLService(serviceUrl, "{allBooks{title}}");
String actualResponse = IOUtils.toString(httpResponse.getEntity().getContent(), StandardCharsets.UTF_8.name());
Response parsedResponse = objectMapper.readValue(actualResponse, Response.class);
assertThat(parsedResponse.getData().getAllBooks()).hasSize(2);

4.3. 模拟响应

使用 MockServer 模拟测试:

String requestQuery = "{allBooks{title}}";
String responseJson = "{\"data\":{\"allBooks\":[{\"title\":\"Title 1\"},{\"title\":\"Title 2\"}]}}";

new MockServerClient("127.0.0.1", 1080)
    .when(
      request()
        .withPath("/graphql")
        .withQueryStringParameter("query", requestQuery),
      exactly(1)
    )
    .respond(
      response()
        .withStatusCode(HttpStatusCode.OK_200.code())
        .withBody(responseJson)
    );

5. 第三方库

5.1. American Express Nodes

基于模型定义构建查询的客户端:

Maven 依赖

<dependency>
    <groupId>com.github.americanexpress.nodes</groupId>
    <artifactId>nodes</artifactId>
    <version>0.5.0</version>
</dependency>

JitPack 仓库

<repository>
    <id>jitpack.io</id>
    <url>https://jitpack.io</url>
</repository>

调用示例

public static GraphQLResponseEntity<Data> callGraphQLService(String url, String query)
  throws IOException {
    GraphQLTemplate graphQLTemplate = new GraphQLTemplate();

    GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
      .url(StringUtils.join(url, "?query=", query))
      .request(Data.class)
      .build();

    return graphQLTemplate.query(requestEntity, Data.class);
}

5.2. GraphQL Java Generator

基于模式生成 Java 代码:

Maven 依赖

<dependency>
    <groupId>com.graphql-java-generator</groupId>
    <artifactId>graphql-java-runtime</artifactId>
    <version>1.18</version>
</dependency>

插件配置

<plugin>
    <groupId>com.graphql-java-generator</groupId>
    <artifactId>graphql-maven-plugin</artifactId>
    <version>1.18</version>
    <executions>
        <execution>
            <goals>
                <goal>generateClientCode</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <packageName>com.baeldung.graphql.generated</packageName>
        <copyRuntimeSources>false</copyRuntimeSources>
        <generateDeprecatedRequestResponse>false</generateDeprecatedRequestResponse>
        <separateUtilityClasses>true</separateUtilityClasses>
    </configuration>
</plugin>

生成代码调用

public List<Book> allBooks(String queryResponseDef, Object... paramsAndValues)
  throws GraphQLRequestExecutionException, GraphQLRequestPreparationException {
    logger.debug("Executing query 'allBooks': {} ", queryResponseDef);
    ObjectResponse objectResponse = getAllBooksResponseBuilder()
      .withQueryResponseDef(queryResponseDef).build();
    return allBooksWithBindValues(objectResponse, 
      graphqlClientUtils.generatesBindVariableValuesMap(paramsAndValues));
}

注意:该库深度集成 Spring 框架

6. 总结

本文深入探讨了从 Java 应用调用 GraphQL 服务的完整流程:

  1. 核心能力

    • 创建基础 GraphQL 服务器
    • 使用标准 HTTP 库发送请求
    • JSON 响应解析为 Java 对象
    • 服务模拟测试
  2. 第三方方案

    • Nodes:模型驱动的查询构建
    • GraphQL Java Generator:模式代码生成
  3. 最佳实践

    • 优先使用类型安全的客户端库
    • 复杂场景考虑代码生成方案
    • 测试阶段善用 MockServer

踩坑提示:避免手动拼接查询字符串,推荐使用库提供的构建器防止注入风险

完整源码参考:GitHub 仓库


原始标题:Make a Call to a GraphQL Service from a Java Application