|
此版本仍在开发中,尚未被认为是稳定的。请使用最新稳定版本 Spring GraphQL 2.0.2! |
客户端
Spring for GraphQL 包括客户端支持,可以在HTTP、WebSocket和RSocket上执行GraphQL请求。
GraphQlClient
GraphQlClient 定义了一种独立于底层传输的 GraphQL 请求常见工作流,因此无论使用哪种传输方式,执行请求的方式都是相同的。
以下特定传输的 GraphQlClient 扩展可用:
每个都定义了一个与传输相关的 Builder。所有构建器都从通用的基础 GraphQlClient Builder 继承,该基础类包含适用于所有传输的选项。
一旦构建了GraphQlClient,您就可以开始发送请求。
通常,GraphQL 操作请求会以文本形式提供。另外,您可以使用通过 DGS Codegen 客户端 API 类 与 DgsGraphQlClient 进行交互,它可以包装上述任意一个 GraphQlClient 扩展。
HTTP 同步
HttpSyncGraphQlClient 使用 RestClient 执行通过阻塞传输契约和拦截器链在 HTTP 上的 GraphQL 请求。
RestClient restClient = RestClient.create("https://spring.io/graphql");
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);
一旦 HttpSyncGraphQlClient 被创建,你就可以使用相同的 API 开始
执行请求,而与底层传输无关。如果你需要更改任何特定于传输的细节,可以在现有的 HttpSyncGraphQlClient 上使用 mutate() 来创建一个新的具有自定义设置的实例:
RestClient restClient = RestClient.create("https://spring.io/graphql");
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.builder(restClient)
.headers((headers) -> headers.setBasicAuth("joe", "..."))
.build();
// Perform requests with graphQlClient...
HttpSyncGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
.headers((headers) -> headers.setBasicAuth("peter", "..."))
.build();
// Perform requests with anotherGraphQlClient...
HTTP
HttpGraphQlClient 使用 WebClient 执行通过非阻塞传输契约和拦截器链的 GraphQL 请求。
WebClient webClient = WebClient.create("https://spring.io/graphql");
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);
一旦 HttpGraphQlClient 被创建,你就可以使用相同的 API 开始
执行请求,而与底层传输无关。如果你需要更改任何特定于传输的细节,可以在现有的 HttpGraphQlClient 上使用 mutate() 来创建一个新的具有自定义设置的实例:
WebClient webClient = WebClient.create("https://spring.io/graphql");
HttpGraphQlClient graphQlClient = HttpGraphQlClient.builder(webClient)
.headers((headers) -> headers.setBasicAuth("joe", "..."))
.build();
// Perform requests with graphQlClient...
HttpGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
.headers((headers) -> headers.setBasicAuth("peter", "..."))
.build();
// Perform requests with anotherGraphQlClient...
WebSocket
WebSocketGraphQlClient通过共享的WebSocket连接执行GraphQL请求。
它基于Spring WebFlux中的
WebSocketClient
构建,您可以如下创建它:
String url = "wss://spring.io/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build();
在与HttpGraphQlClient相反的情况下,WebSocketGraphQlClient是连接导向的,这意味着它需要在发送任何请求之前建立一个连接。当你开始发送请求时,连接会透明地被建立起来。或者,可以使用客户端的start()方法,在发送任何请求之前显式地建立连接。
除了是连接导向的,WebSocketGraphQlClient还是多路复用的。
它维护一个单一的、共享的连接用于所有请求。如果连接丢失,
它会在下一个请求时或再次调用start()时重新建立。你也可以使用客户端的stop()方法,该方法会取消正在进行的请求,关闭
连接,并拒绝新的请求。
使用每个服务器上的单个WebSocketGraphQlClient实例,以便为所有对该服务器的请求共享一个连接。每个客户端实例都会建立自己的连接,这通常不是针对单个服务器的意图。 |
一旦 WebSocketGraphQlClient 被创建,你就可以使用相同的 API 开始
执行请求,而与底层传输无关。如果你需要更改任何特定于传输的细节,可以在现有的 WebSocketGraphQlClient 上使用 mutate() 来创建一个新的具有自定义设置的实例:
String url = "wss://spring.io/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
.headers((headers) -> headers.setBasicAuth("joe", "..."))
.build();
// Use graphQlClient...
WebSocketGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
.headers((headers) -> headers.setBasicAuth("peter", "..."))
.build();
// Use anotherGraphQlClient...
WebSocketGraphQlClient 支持在不发送或接收其他消息时,定期发送ping消息以保持连接活跃。您可以按照以下方式启用该功能:
String url = "wss://spring.io/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
.keepAlive(Duration.ofSeconds(30))
.build();
拦截器
GraphQL over WebSocket 协议定义了若干连接导向的消息,除了执行请求。例如,在连接开始时,客户端发送 "connection_init",服务器响应 "connection_ack"。
对于WebSocket传输特定的拦截,你可以创建一个
WebSocketGraphQlClientInterceptor:
static class MyInterceptor implements WebSocketGraphQlClientInterceptor {
@Override
public Mono<Object> connectionInitPayload() {
// ... the "connection_init" payload to send
}
@Override
public Mono<Void> handleConnectionAck(Map<String, Object> ackPayload) {
// ... the "connection_ack" payload received
}
}
注册上述拦截器,就像注册其他
GraphQlClientInterceptor一样,并且也可以用来拦截GraphQL请求,但请注意,只能有一个类型的
WebSocketGraphQlClientInterceptor拦截器。
RSocket
RSocketGraphQlClient 使用 RSocketRequester 执行基于 RSocket 请求的 GraphQL 请求。
URI uri = URI.create("wss://localhost:8080/rsocket");
WebsocketClientTransport transport = WebsocketClientTransport.create(uri);
RSocketGraphQlClient client = RSocketGraphQlClient.builder()
.clientTransport(transport)
.build();
在与HttpGraphQlClient相反的情况下,RSocketGraphQlClient是连接导向的,这意味着它需要在发送任何请求之前建立一个会话。当您开始发送请求时,会话将透明地建立。或者,可以使用客户端的start()方法,在发送任何请求之前明确地建立会话。
RSocketGraphQlClient 也被多路复用。它为所有请求维护一个单一的、共享的会话。
如果会话丢失,它会在下一次请求或再次调用 start() 时重新建立。你还可以使用客户端的 stop() 方法,该方法取消正在进行中的请求、关闭会话,并拒绝新的请求。
使用每个服务器上的单个RSocketGraphQlClient实例,以便为该服务器的所有请求共享一个会话。每个客户端实例都会建立自己的连接,这通常不是单个服务器的意图。 |
一旦 RSocketGraphQlClient 被创建,您就可以使用相同的 API 开始
执行请求,而不依赖于底层的传输。
请求
一旦您拥有 GraphQlClient,即可开始通过
检索 或 执行
方法发起请求。
检索
以下代码用于检索并解码查询数据:
-
Sync
-
Non-Blocking
String document =
"""
{
project(slug:"spring-framework") {
name
releases {
version
}
}
}
""";
Project project = graphQlClient.document(document) (1)
.retrieveSync("project") (2)
.toEntity(Project.class); (3)
String document =
"""
{
project(slug:"spring-framework") {
name
releases {
version
}
}
}
""";
Mono<Project> project = graphQlClient.document(document) (1)
.retrieve("project") (2)
.toEntity(Project.class); (3)
| 1 | 执行的操作。 |
| 2 | 响应映射中的 "data" 键下的路径用于解码。 |
| 3 | 将路径上的数据解码为目标类型。 |
The input document is a String that could be a literal or produced through a code generated request object. You can also define documents in files and use a Document Source to resolve them by file name.
The path is relative to the "data" key and uses a simple dot (".") separated notation
for nested fields with optional array indices for list elements, e.g. "project.name"
or "project.releases[0].version".
解码可以返回 FieldAccessException,如果给定的路径不存在,或者字段值为 null 并且存在错误。FieldAccessException 可以访问响应和字段:
-
Sync
-
Non-Blocking
try {
Project project = graphQlClient.document(document)
.retrieveSync("project")
.toEntity(Project.class);
return project;
}
catch (FieldAccessException ex) {
ClientGraphQlResponse response = ex.getResponse();
// ...
ClientResponseField field = ex.getField();
// return fallback value
return new Project();
}
Mono<Project> projectMono = graphQlClient.document(document)
.retrieve("project")
.toEntity(Project.class)
.onErrorResume(FieldAccessException.class, (ex) -> {
ClientGraphQlResponse response = ex.getResponse();
// ...
ClientResponseField field = ex.getField();
// return fallback value
return Mono.just(new Project());
});
执行
Retrieve 只是一个从响应映射中的单个路径解码的快捷方式。如需更多控制,请使用 execute 方法并处理响应:
例如:
-
Sync
-
Non-Blocking
ClientGraphQlResponse response = graphQlClient.document(document).executeSync();
if (!response.isValid()) {
// Request failure... (1)
}
ClientResponseField field = response.field("project");
if (field.getValue() == null) {
if (field.getErrors().isEmpty()) {
// Optional field set to null... (2)
}
else {
// Field failure... (3)
}
}
Project project = field.toEntity(Project.class); (4)
Mono<Project> projectMono = graphQlClient.document(document)
.execute()
.map((response) -> {
if (!response.isValid()) {
// Request failure... (1)
}
ClientResponseField field = response.field("project");
if (field.getValue() == null) {
if (field.getErrors().isEmpty()) {
// Optional field set to null... (2)
}
else {
// Field failure... (3)
}
}
return field.toEntity(Project.class); (4)
});
| 1 | The response does not have data, only errors |
| 2 | 字段在它的DataFetcher中被设置为null |
| 3 | 字段值为null并且关联有错误 |
| 4 | 解码给定路径下的数据 |
文档源码
来自Java Spring框架文档网站的内容是一个请求,它可能定义在一个局部变量或常量中,或者通过生成的请求对象产生。
您也可以在类路径下的"graphql-documents/"目录下创建扩展名为.graphql或.gql的文档文件,并通过文件名引用它们。
例如,在src/main/resources/graphql-documents目录下有一个名为projectReleases.graphql的文件,其内容为:
query projectReleases($slug: ID!) {
project(slug: $slug) {
name
releases {
version
}
}
}
然后你可以:<br>
Project project = graphQlClient.documentName("projectReleases") (1)
.variable("slug", "spring-framework") (2)
.retrieveSync("projectReleases.project")
.toEntity(Project.class);
| 1 | 从 "projectReleases.graphql" 加载文档 |
| 2 | 提供变量值。 |
"JS GraphQL"插件支持IntelliJ中的GraphQL查询文件,并提供代码完成功能。
您可以使用 GraphQlClient 构建器来自定义用于按名称加载文档的
DocumentSource。
订阅请求
订阅请求需要一种能够流式传输数据的客户端传输方式。
您将需要创建一个GraphQlClient,它支持这一点:
-
HttpGraphQLClient 与 Server-Sent Events
-
WebSocketGraphQlClient 使用 WebSocket
-
RSocketGraphQlClient 通过 RSocket
检索
要启动订阅流,请使用retrieveSubscription,这类似于单次响应的检索,但返回一个响应流,每个响应解码为某些数据:
Flux<String> greetingFlux = client.document("subscription { greetings }")
.retrieveSubscription("greeting")
.toEntity(String.class);
The Flux 可能会终止于 SubscriptionErrorException,如果订阅从服务器端以“错误”消息结束。异常提供了访问从“错误”消息解码而来的 GraphQL 错误的功能。
The Flux 可能会与 GraphQlTransportException 结尾,例如
WebSocketDisconnectedException 如果底层连接被关闭或丢失。在这种情况下,您可以使用 retry 操作符重新启动订阅。
要从客户端结束订阅,必须取消 Flux,随后 WebSocket 传输层会向服务器发送一条“完成”消息。如何取消 Flux 取决于其使用方式。某些操作符(如 take 或 timeout)自身会取消 Flux。如果您使用 Subscriber 订阅 Flux,则可以获得对 Subscription 的引用并通过它进行取消。onSubscribe 操作符也提供了对 Subscription 的访问。
执行
Retrieve 只是一个快捷方式,用于从每个响应映射中的单个路径进行解码。如需更多控制,请使用 executeSubscription 方法并直接处理每个响应:
Flux<String> greetingFlux = client.document("subscription { greetings }")
.executeSubscription()
.map((response) -> {
if (!response.isValid()) {
// Request failure...
}
ClientResponseField field = response.field("project");
if (field.getValue() == null) {
if (field.getErrors().isEmpty()) {
// Optional field set to null...
}
else {
// Field failure...
}
}
return field.toEntity(String.class);
});
拦截
对于使用 GraphQlClient.SyncBuilder 创建的阻塞传输,您需要创建一个 SyncGraphQlClientInterceptor 以拦截客户端的所有请求:
import org.springframework.graphql.client.ClientGraphQlRequest;
import org.springframework.graphql.client.ClientGraphQlResponse;
import org.springframework.graphql.client.SyncGraphQlClientInterceptor;
public class SyncInterceptor implements SyncGraphQlClientInterceptor {
@Override
public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) {
// ...
return chain.next(request);
}
}
使用 `0` 创建的非阻塞传输,您需要创建一个 GraphQlClientInterceptor 以拦截客户端的所有请求:
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.graphql.client.ClientGraphQlRequest;
import org.springframework.graphql.client.ClientGraphQlResponse;
import org.springframework.graphql.client.GraphQlClientInterceptor;
public class MyInterceptor implements GraphQlClientInterceptor {
@Override
public Mono<ClientGraphQlResponse> intercept(ClientGraphQlRequest request, Chain chain) {
// ...
return chain.next(request);
}
@Override
public Flux<ClientGraphQlResponse> interceptSubscription(ClientGraphQlRequest request, SubscriptionChain chain) {
// ...
return chain.next(request);
}
}
创建拦截器后,通过客户端构建器进行注册。例如:
URI url = URI.create("wss://localhost:8080/graphql");
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
.interceptor(new MyInterceptor())
.build();
DGS 代码生成
作为提供操作(如突变、查询或订阅)的替代方法,您可以使用 DGS Codegen 库生成客户端API类,从而可以使用流畅的API来定义请求。
Spring for GraphQL提供DgsGraphQlClient
,可以包装任何GraphQlClient并帮助使用生成的客户端API类准备请求。
例如,给定以下模式:
type Query {
books: [Book]
}
type Book {
id: ID
name: String
}
您可以执行如下请求:
HttpGraphQlClient client = ... ;
DgsGraphQlClient dgsClient = DgsGraphQlClient.create(client); (1)
List<Book> books = dgsClient.request(new BooksGraphQLQuery()) (2)
.projection(new BooksProjectionRoot<>().id().name()) (3)
.retrieveSync("books")
.toEntityList(Book.class);
| 1 | - 创建 DgsGraphQlClient 可以通过包装任何 GraphQlClient 实现。 |
| 2 | - 指定请求的操作。 |
| 3 | - 定义选择集。 |