Spring for GraphQL 包括对通过 HTTP 执行 GraphQL 请求的客户端支持, WebSocket 和 RSocket 的 Socket 的 Socket 和 RSocket 的 Socket 的 Socket
GraphQlClient
GraphQlClient为 GraphQL 请求定义独立于底层的通用工作流程
transport,因此无论使用哪种 transport,您执行请求的方式都是相同的。
可以使用以下特定于传输的扩展:GraphQlClient
每个 define a 都包含与传输相关的选项。所有构建器都扩展了
来自通用的基本 GraphQlClient Builder,具有适用于所有传输的选项。Builder
构建完成后,您就可以开始提出请求了。GraphQlClient
通常,请求的 GraphQL 操作以文本形式提供。或者,您
可以通过 DgsGraphQlClient 使用 DGS Codegen 客户端 API 类,它可以包装任何
。GraphQlClient
HTTP 同步
HttpSyncGraphQlClient使用 RestClient 通过阻塞传输协定和
拦截 器。
RestClient restClient = ... ;
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);
创建后,您可以开始使用相同的 API 执行请求,独立于底层
运输。如果您需要更改任何特定于传输的详细信息,请在
existing 创建具有自定义设置的新实例:HttpSyncGraphQlClientmutate()HttpSyncGraphQlClient
RestClient restClient = ... ;
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 执行
通过非阻塞传输协定和
拦截 器。
WebClient webClient = ... ;
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);
创建后,您可以开始使用相同的 API 执行请求,独立于底层
运输。如果您需要更改任何特定于传输的详细信息,请在
existing 创建具有自定义设置的新实例:HttpGraphQlClientmutate()HttpGraphQlClient
WebClient webClient = ... ;
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://localhost:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build();
与 相比,它是面向连接的,
这意味着它需要在发出任何请求之前建立连接。开始时
要发出请求,将透明地建立连接。或者,使用
client's 方法在任何请求之前显式建立连接。HttpGraphQlClientWebSocketGraphQlClientstart()
除了面向连接之外,还进行了多路复用。
它为所有请求维护一个共享连接。如果连接丢失,
它会在下一个请求或 if 再次调用时重新建立。您还可以
使用客户端的方法取消正在进行的请求,关闭
connection 并拒绝新请求。WebSocketGraphQlClientstart()stop()
为每个服务器使用单个实例,以便获得
单个共享连接,用于对该服务器的所有请求。每个客户端实例
建立自己的连接,这通常不是单个服务器的意图。WebSocketGraphQlClient |
创建后,您可以开始使用相同的 API 执行请求,独立于底层
运输。如果您需要更改任何特定于传输的详细信息,请在
existing 创建具有自定义设置的新实例:WebSocketGraphQlClientmutate()WebSocketGraphQlClient
URI url = ... ;
WebSocketClient client = ... ;
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 消息以保持连接
当没有发送或接收其他消息时处于活动状态。您可以按如下方式启用它:
URI url = ... ;
WebSocketClient client = ... ;
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
}
}
将上述拦截器注册为任何其他拦截器,并使用它来拦截 GraphQL 请求,但请注意
最多可以是一个类型为 的拦截器 。GraphQlClientInterceptorWebSocketGraphQlClientInterceptor
RSocket 系列
RSocketGraphQlClient使用 RSocketRequester 执行 GraphQL 请求而不是 RSocket 请求。
URI uri = URI.create("wss://localhost:8080/rsocket");
WebsocketClientTransport transport = WebsocketClientTransport.create(url);
RSocketGraphQlClient client = RSocketGraphQlClient.builder()
.clientTransport(transport)
.build();
与 相比,它是面向连接的,
这意味着它需要在发出任何请求之前建立一个会话。开始时
要发出请求,会话将以透明方式建立。或者,使用
client's 方法显式建立会话。HttpGraphQlClientRSocketGraphQlClientstart()
RSocketGraphQlClient也是多路复用的。它维护一个共享会话
所有请求。如果会话丢失,则会在下一个请求或再次调用时重新建立会话。您还可以使用 client's 方法,该方法将
in-progress 请求,关闭会话并拒绝新请求。start()stop()
为每个服务器使用单个实例,以便获得
单个共享会话,用于对该服务器的所有请求。每个客户端实例
建立自己的连接,这通常不是单个服务器的意图。RSocketGraphQlClient |
创建后,您可以开始使用相同的 API 执行请求,独立于底层
运输。RSocketGraphQlClient
架构工人
GraphQlClient定义具有
所有扩展的构建器。目前,它允许您配置:BaseBuilder
-
DocumentSource从文件中加载请求的文档的策略 -
拦截已执行的请求
BaseBuilder进一步扩展了以下内容:
-
SyncBuilder- 使用 's 链阻塞执行堆栈。SyncGraphQlInterceptor -
Builder- 具有 's 链的非阻塞执行堆栈。GraphQlInterceptor
为每个服务器使用单个实例,以便获得
单个共享连接,用于对该服务器的所有请求。每个客户端实例
建立自己的连接,这通常不是单个服务器的意图。WebSocketGraphQlClient |
为每个服务器使用单个实例,以便获得
单个共享会话,用于对该服务器的所有请求。每个客户端实例
建立自己的连接,这通常不是单个服务器的意图。RSocketGraphQlClient |
请求
拥有 GraphQlClient 后,您可以开始通过 retrieve 或 execute 方法执行请求。
取回
下面检索和解码查询的数据:
-
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> projectMono = graphQlClient.document(document) (1)
.retrieve("project") (2)
.toEntity(Project.class); (3)
| 1 | 要执行的操作。 |
| 2 | 响应映射中要从中解码的 “data” 键下的路径。 |
| 3 | 解码目标类型的路径中的数据。 |
输入文档可以是文本或通过代码生成的
generated request 对象。您还可以在文件中定义文档,并使用 Document Source 按文件名重新指定它们。String
该路径是相对于 “data” 键的,并使用简单的点 (“.”) 分隔表示法
对于具有列表元素的可选数组索引的嵌套字段,例如 或。"project.name""project.releases[0].version"
如果给定路径不存在,或者
field 值为 和 有错误。 提供对
response 和字段:FieldAccessExceptionnullFieldAccessException
-
Sync
-
Non-Blocking
try {
Project project = graphQlClient.document(document)
.retrieveSync("project")
.toEntity(Project.class);
}
catch (FieldAccessException ex) {
ClientGraphQlResponse response = ex.getResponse();
// ...
ClientResponseField field = ex.getField();
// ...
}
Mono<Project> projectMono = graphQlClient.document(document)
.retrieve("project")
.toEntity(Project.class)
.onErrorResume(FieldAccessException.class, ex -> {
ClientGraphQlResponse response = ex.getResponse();
// ...
ClientResponseField field = ex.getField();
// ...
});
执行
Retrieve 只是从
响应映射。如需更多控制,请使用该方法并处理响应:execute
例如:
-
Sync
-
Non-Blocking
ClientGraphQlResponse response = graphQlClient.document(document).executeSync();
if (!response.isValid()) {
// Request failure... (1)
}
ClientResponseField field = response.field("project");
if (!field.hasValue()) {
if (field.getError() != null) {
// Field failure... (2)
}
else {
// Optional field set to null... (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.hasValue()) {
if (field.getError() != null) {
// Field failure... (2)
}
else {
// Optional field set to null... (3)
}
}
return field.toEntity(Project.class); (4)
});
| 1 | 响应没有数据,只有错误 |
| 2 | 字段是 和 具有关联错误null |
| 3 | 字段,该字段由其nullDataFetcher |
| 4 | 解码给定路径上的数据 |
文档源
请求的文档是可以在局部变量中定义的 a,或者
constant,或者它可能通过代码生成的请求对象生成。String
您还可以在类路径中创建带有扩展名或 under 的文档文件,并通过文件名引用它们。.graphql.gql"graphql-documents/"
例如,给定一个名为 in 的文件,其中包含内容:projectReleases.graphqlsrc/main/resources/graphql-documents
query projectReleases($slug: ID!) {
project(slug: $slug) {
name
releases {
version
}
}
}
然后,您可以:
Project project = graphQlClient.documentName("projectReleases") (1)
.variable("slug", "spring-framework") (2)
.retrieveSync()
.toEntity(Project.class);
| 1 | 从 “projectReleases.graphql” 加载文档 |
| 2 | 提供变量值。 |
此方法也适用于为查询加载片段。
片段是可重用的字段选择集,可避免在请求文档中重复。
例如,我们可以在多个查询中使用一个 fragment:…releases
query frameworkReleases {
project(slug: "spring-framework") {
name
...releases
}
}
query graphqlReleases {
project(slug: "spring-graphql") {
name
...releases
}
}
此片段可以在单独的文件中定义以供重用:
fragment releases on Project {
releases {
version
}
}
然后,您可以沿查询文档发送此片段:
Project project = graphQlClient.documentName("projectReleases") (1)
.fragmentName("releases") (2)
.retrieveSync()
.toEntity(Project.class);
| 1 | 从 “projectReleases.graphql” 加载文档 |
| 2 | 从 “releases.graphql” 加载片段并将其附加到文档中 |
IntelliJ 的“JS GraphQL”插件支持具有代码完成的 GraphQL 查询文件。
您可以使用 Builder 自定义 以按名称加载文档。GraphQlClientDocumentSource
| 1 | 要执行的操作。 |
| 2 | 响应映射中要从中解码的 “data” 键下的路径。 |
| 3 | 解码目标类型的路径中的数据。 |
| 1 | 响应没有数据,只有错误 |
| 2 | 字段是 和 具有关联错误null |
| 3 | 字段,该字段由其nullDataFetcher |
| 4 | 解码给定路径上的数据 |
| 1 | 从 “projectReleases.graphql” 加载文档 |
| 2 | 提供变量值。 |
| 1 | 从 “projectReleases.graphql” 加载文档 |
| 2 | 从 “releases.graphql” 加载片段并将其附加到文档中 |
订阅请求
订阅请求需要能够流式传输数据的客户端传输。
您需要创建一个支持此功能的 API:GraphQlClient
取回
要启动订阅流,请使用 which 类似于 retrieve 来获取单个响应,但返回
响应,每个响应都解码为一些数据:retrieveSubscription
Flux<String> greetingFlux = client.document("subscription { greetings }")
.retrieveSubscription("greeting")
.toEntity(String.class);
如果订阅从
服务器端显示 “error” 消息。异常提供对 GraphQL 错误的访问
从 “error” 消息解码。FluxSubscriptionErrorException
如果底层连接已关闭或丢失,则可能会终止。在那个
的情况下,您可以使用 Operator 重新启动订阅。FluxGraphQlTransportExceptionWebSocketDisconnectedExceptionretry
要从客户端结束订阅,必须取消 ,然后
WebSocket 传输向服务器发送 “complete” 消息。如何取消 取决于它的使用方式。一些运算符,例如 or 自己
取消 .如果您使用 订阅 ,则可以获得
引用 and cancel 通过它。操作员还
提供对 的访问。FluxFluxtaketimeoutFluxFluxSubscriberSubscriptiononSubscribeSubscription
执行
Retrieve 只是从每个
响应映射。要获得更多控制,请使用 method 并处理每个
直接响应:executeSubscription
Flux<String> greetingFlux = client.document("subscription { greetings }")
.executeSubscription()
.map(response -> {
if (!response.isValid()) {
// Request failure...
}
ClientResponseField field = response.field("project");
if (!field.hasValue()) {
if (field.getError() != null) {
// Field failure...
}
else {
// Optional field set to null... (3)
}
}
return field.toEntity(String.class)
});
拦截
对于使用 创建的阻止传输,您可以创建一个以拦截通过客户端的所有请求:GraphQlClient.SyncBuilderSyncGraphQlClientInterceptor
static class MyInterceptor implements SyncGraphQlClientInterceptor {
@Override
public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) {
// ...
return chain.next(request);
}
}
对于使用 创建的非阻塞传输,您可以创建一个以通过客户端拦截所有请求:GraphQlClient.BuilderGraphQlClientInterceptor
static 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);
}
}
创建侦听器后,通过 Client 端构建器注册它。例如:
URI url = ... ;
WebSocketClient client = ... ;
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
.interceptor(new MyInterceptor())
.build();
DGS 代码生成
作为将 mutation、query 或 subscription 等操作作为 文本中,您可以使用 DGS Codegen 库 生成客户端 API 类,让您使用 Fluent API 来定义请求。
Spring for GraphQL 提供了 DgsGraphQlClient,它包装了任何 DgsGraphQlClient 并帮助使用生成的客户端准备请求
API 类。GraphQlClient
例如,给定以下架构:
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()
.toEntityList(Book.class);
| 1 | - 通过包装任何 .DgsGraphQlClientGraphQlClient |
| 2 | - 指定请求的操作。 |
| 3 | - 定义选择集。 |
| 1 | - 通过包装任何 .DgsGraphQlClientGraphQlClient |
| 2 | - 指定请求的操作。 |
| 3 | - 定义选择集。 |