前言
GraphQL 是一種 API 查詢語言, 簡單來說就是一種描述客戶端如何向服務器端請求數據的 API 語法,和 RESTful 規范類似。

GraphQL 規范由 Facebook 在2015年開源,設計初衷是想要用類似圖的方式表示數據,即不像在 RESTful 中,數據被各個 API endpoint 所分割,而是有關聯和層次結構的被組織在一起,更多相關知識可以去 GraphQL 官網 了解。
GraphQL-Java
介紹
GraphQL 只是一種規范,還需要有具體的語言庫來實現這種規范,就像 FastJson 實現了 JSON 規范一樣,GraphQL 在java中的一種實現是 graphql-java,更多語言對 GraphQL 的支持可以 看這里。
簡單使用
引入maven依賴
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>11.0</version>
</dependency>
客戶端使用
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.StaticDataFetcher;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
public class Client {
public static void main(String[] args) {
//定義schema,可以類比xml定義
String schema = "type Query{hello: String}";
//解析schema
SchemaParser schemaParser = new SchemaParser();
TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);
//加載服務端數據
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder.dataFetcher("hello", new StaticDataFetcher("world")))
.build();
SchemaGenerator schemaGenerator = new SchemaGenerator();
GraphQLSchema graphQLSchema = schemaGenerator
.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
//構建GraphQL實例並執行查詢腳本
GraphQL build = GraphQL.newGraphQL(graphQLSchema).build();
ExecutionResult executionResult = build.execute("{hello}");
System.out.println(executionResult.getData().toString());//{hello=world}
}
}
基本處理流程圖如下

想了解更多關於定義schema的介紹,可以查看 官方文檔-Queries and Mutations 。
SpringBoot整合GraphQL
引入maven依賴
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-spring-boot-starter-webmvc</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
</dependency>
Guava不是必需的,這是使用Guava來簡化我們的代碼,graphql-java-spring-boot-starter-webmvc 自動裝配GraphQLController
這是一個統一處理的Controller,源碼如下
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.ExecutionResult;
import graphql.Internal;
import graphql.spring.web.servlet.ExecutionResultHandler;
import graphql.spring.web.servlet.GraphQLInvocation;
import graphql.spring.web.servlet.GraphQLInvocationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@RestController
@Internal
public class GraphQLController {
@Autowired
GraphQLInvocation graphQLInvocation;
@Autowired
ExecutionResultHandler executionResultHandler;
@Autowired
ObjectMapper objectMapper;
@RequestMapping(value = "${graphql.url:graphql}",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object graphqlPOST(@RequestBody GraphQLRequestBody body,
WebRequest webRequest) {
String query = body.getQuery();
if (query == null) {
query = "";
}
CompletableFuture<ExecutionResult> executionResult = graphQLInvocation.invoke(new GraphQLInvocationData(query, body.getOperationName(), body.getVariables()), webRequest);
return executionResultHandler.handleExecutionResult(executionResult);
}
@RequestMapping(value = "${graphql.url:graphql}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object graphqlGET(
@RequestParam("query") String query,
@RequestParam(value = "operationName", required = false) String operationName,
@RequestParam(value = "variables", required = false) String variablesJson,
WebRequest webRequest) {
CompletableFuture<ExecutionResult> executionResult = graphQLInvocation.invoke(new GraphQLInvocationData(query, operationName, convertVariablesJson(variablesJson)), webRequest);
return executionResultHandler.handleExecutionResult(executionResult);
}
private Map<String, Object> convertVariablesJson(String jsonMap) {
if (jsonMap == null) return Collections.emptyMap();
try {
return objectMapper.readValue(jsonMap, Map.class);
} catch (IOException e) {
throw new RuntimeException("Could not convert variables GET parameter: expected a JSON map", e);
}
}
}
在這個基礎上我們只需要定義 schema 和實例化 GraphQL 對象就可以了,具體流程可以參考 Getting started with GraphQL Java and Spring Boot。
定義schema文件
文件 schema.graphqls
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
定義DataFetcher
import com.google.common.collect.ImmutableMap;
import graphql.schema.DataFetcher;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
@Component
public class GraphQLDataFetchers {
private static List<Map<String, String>> books = Arrays.asList(
ImmutableMap.of("id", "book-1",
"name", "Harry Potter and the Philosopher's Stone",
"pageCount", "223",
"authorId", "author-1"),
ImmutableMap.of("id", "book-2",
"name", "Moby Dick",
"pageCount", "635",
"authorId", "author-2"),
ImmutableMap.of("id", "book-3",
"name", "Interview with the vampire",
"pageCount", "371",
"authorId", "author-3")
);
private static List<Map<String, String>> authors = Arrays.asList(
ImmutableMap.of("id", "author-1",
"firstName", "Joanne",
"lastName", "Rowling"),
ImmutableMap.of("id", "author-2",
"firstName", "Herman",
"lastName", "Melville"),
ImmutableMap.of("id", "author-3",
"firstName", "Anne",
"lastName", "Rice")
);
public DataFetcher getBookByIdDataFetcher() {
return dataFetchingEnvironment -> {
String bookId = dataFetchingEnvironment.getArgument("id");
return books
.stream()
.filter(book -> book.get("id").equals(bookId))
.findFirst()
.orElse(null);
};
}
public DataFetcher getAuthorDataFetcher() {
return dataFetchingEnvironment -> {
Map<String, String> book = dataFetchingEnvironment.getSource();
String authorId = book.get("authorId");
return authors
.stream()
.filter(author -> author.get("id").equals(authorId))
.findFirst()
.orElse(null);
};
}
}
DataFetcher表示當執行查詢腳本時,獲取某一個屬性的數據所對應的業務處理,復雜的業務邏輯就在這塊,這里我們使用靜態數據來模擬,真實環境應該查詢數據庫來獲取數據。
解析schema並創建GraphQL
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.TypeRuntimeWiring;
import java.nio.charset.StandardCharsets;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
@Component
public class GraphQLProvider implements InitializingBean {
@Autowired
private GraphQLDataFetchers graphQLDataFetchers;
private GraphQLSchema graphQLSchema;
private GraphQLSchema buildSchema(String sdl) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
RuntimeWiring runtimeWiring = buildWiring();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
.type(TypeRuntimeWiring.newTypeWiring("Book")
.dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
.build();
}
@Bean
public GraphQL graphQL() {
return GraphQL.newGraphQL(graphQLSchema).build();
}
@Override
public void afterPropertiesSet() throws Exception {
Resource resource = new DefaultResourceLoader().getResource("schema.graphqls");
String sdl = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
graphQLSchema = buildSchema(sdl);
}
}
使用Postman客戶端請求數據

最終請求的 content-type 還是 application/json,postman只是將數據做了一下轉換,
{
bookById(id: "book-1"){
id
name
pageCount
}
}
到了服務器,數據就變成了

leetcode答題網站中就大量使用到了 GraphQL 這種請求方式,

參考
GraphQL文檔官網
Getting started with GraphQL Java and Spring Boot
GraphQL java工程化實踐
graphQl + SpringBoot 入門
Spring GraphQL成為Spring頂級項目,將發布第一個里程碑版本