此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring GraphQL 1.4.1! |
客户端
Spring for GraphQL 包括客户端支持通过 HTTP、WebSocket 和 RSocket 执行 GraphQL 请求。
GraphQlClient
GraphQlClient
为 GraphQL 请求定义了一个独立于底层传输的通用工作流程,因此无论使用哪种传输,执行请求的方式都是相同的。
以下特定于传输GraphQlClient
扩展可用:
每个定义了一个Builder
具有与传输相关的选项。所有构建器都扩展从通用的基本 GraphQlClientBuilder
选项适用于所有运输。
一次GraphQlClient
已构建,您可以开始提出请求。
通常,请求的 GraphQL作以文本形式提供。或者,您可以通过 DgsGraphQlClient 使用 DGS Codegen 客户端 API 类,该类可以包装任何 以上GraphQlClient
扩展。
HTTP 同步
HttpSyncGraphQlClient
使用 RestClient 通过阻塞传输合约和 拦截 器。
RestClient restClient = RestClient.create("https://spring.io/graphql");
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);
一次HttpSyncGraphQlClient
创建时,您可以开始使用相同的 API 执行请求,而不受底层 运输。 如果您需要更改任何特定于传输的详细信息,请使用mutate()
在 现存HttpSyncGraphQlClient
要使用自定义设置创建新实例,请执行以下作:
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 执行通过非阻塞传输合约和 拦截 器。
WebClient webClient = WebClient.create("https://spring.io/graphql");
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);
一次HttpGraphQlClient
创建时,您可以开始使用相同的 API 执行请求,而不受底层 运输。 如果您需要更改任何特定于传输的详细信息,请使用mutate()
在 现存HttpGraphQlClient
要使用自定义设置创建新实例,请执行以下作:
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...
Web套接字
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 执行请求,而不受底层 运输。 如果您需要更改任何特定于传输的详细信息,请使用mutate()
在 现存WebSocketGraphQlClient
要使用自定义设置创建新实例,请执行以下作:
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
.
RS袜子
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()
取消的方法
in-progress 请求,关闭会话,并拒绝新请求。
使用单个RSocketGraphQlClient 实例,以便拥有
对该服务器的所有请求的单个共享会话。每个客户端实例
建立自己的连接,这通常不是单个服务器的意图。 |
一次RSocketGraphQlClient
创建时,您可以开始使用相同的 API 执行请求,而不受底层
运输。
架构工人
GraphQlClient
定义父级BaseBuilder
具有
所有扩展的构建者。目前,它允许您配置:
-
DocumentSource
从文件加载请求文档的策略 -
拦截已执行的请求
BaseBuilder
进一步扩展了以下内容:
-
SyncBuilder
- 使用链SyncGraphQlInterceptor
的。 -
Builder
- 具有链的非阻塞执行堆栈GraphQlInterceptor
的。
请求
一旦你有一个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 | 在目标类型的路径处解码数据。 |
输入文档是String
可以是文字,也可以是通过代码生成的
生成的请求对象。您还可以在文件中定义文档,并使用文档源按文件名重新定位它们。
该路径相对于“data”键,并使用简单的点 (“.”) 分隔表示法
对于具有列表元素可选数组索引的嵌套字段,例如"project.name"
或"project.releases[0].version"
.
解码可能导致FieldAccessException
如果给定的路径不存在,或者
字段值为null
并且有一个错误。FieldAccessException
提供对
response 和字段:
-
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());
});
如果该字段存在,但无法解码为请求的类型,则普通GraphQlClientException
而是被抛出。
执行
检索只是从单个路径解码的快捷方式
响应图。要获得更多控制,请使用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 | 响应没有数据,只有错误 |
2 | 设置为null 通过其DataFetcher |
3 | 字段是null 并存在相关错误 |
4 | 在给定路径上解码数据 |
文档来源
请求的文档是String
可以在局部变量中定义或
常量,或者它可以通过代码生成的请求对象生成。
您还可以创建带有扩展名的文档文件.graphql
或.gql
下"graphql-documents/"
并按文件名引用它们。
例如,给定一个名为projectReleases.graphql
在src/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("projectReleases.project")
.toEntity(Project.class);
1 | 从“projectReleases.graphql”加载文档 |
2 | 提供变量值。 |
IntelliJ 的“JS GraphQL”插件支持具有代码补全功能的 GraphQL 查询文件。
您可以使用GraphQlClient
构建器来自定义DocumentSource
用于按名称加载文档。
订阅请求
订阅请求需要能够流式传输数据的客户端传输。
您需要创建一个GraphQlClient
支持这一点:
-
HttpGraphQlClient 与服务器发送的事件
-
WebSocketGraphQlClient 与 WebSocket
-
RSocketGraphQlClient 与 RSocket
取回
要启动订阅流,请使用retrieveSubscription
这类似于对单个响应的检索,但返回
响应,每个响应都解码为一些数据:
Flux<String> greetingFlux = client.document("subscription { greetings }")
.retrieveSubscription("greeting")
.toEntity(String.class);
这Flux
可以以SubscriptionErrorException
如果订阅从
服务器端显示“错误”消息。该异常提供对 GraphQL 错误的访问
从“错误”消息中解码。
这Flux
可以以GraphQlTransportException
如WebSocketDisconnectedException
如果底层连接已关闭或丢失。在那
案例,您可以使用retry
运算符以重新启动订阅。
要从客户端结束订阅,请Flux
必须取消,反过来
WebSocket 传输向服务器发送“完整”消息。如何取消Flux
取决于它的使用方式。一些运算符,例如take
或timeout
他们自己
取消Flux
.如果您订阅了Flux
使用Subscriber
,您可以获得一个
引用Subscription
并通过它取消。这onSubscribe
运算符也
提供对Subscription
.
执行
检索只是从每个路径解码的快捷方式
响应图。要获得更多控制,请使用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);
}
}
对于使用GraphQlClient.Builder
时,您创建了一个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();
可选输入
默认情况下,GraphQL 中的输入类型是可为 null 且可选的。
输入值(或其任何字段)可以设置为null
字面意思,或者根本不提供。
这种区别对于具有突变的部分更新很有用,其中基础数据也可能是
设置为null
或者根本没有相应地改变。
类似于ArgumentValue<T> support in controllers
,
我们可以用ArgumentValue<T>
或者在客户端的类属性级别使用它。
给定一个ProjectInput
类,例如:
import org.springframework.graphql.data.ArgumentValue;
public record ProjectInput(String id, ArgumentValue<String> name) {
}
我们可以使用客户端发送变更请求:
public void updateProject() {
ProjectInput projectInput = new ProjectInput("spring-graphql",
ArgumentValue.ofNullable("Spring for GraphQL")); (1)
ClientGraphQlResponse response = this.graphQlClient.document("""
mutation updateProject($project: ProjectInput!) {
updateProject($project: $project) {
id
name
}
}
""")
.variables(Map.of("project", projectInput))
.executeSync();
}
1 | 我们可以使用ArgumentValue.omitted() 相反,忽略此字段 |
为此,客户端必须使用 Jackson 进行 JSON(反)序列化,并且必须进行配置
使用org.springframework.graphql.client.json.GraphQlJacksonModule
.
这可以在底层 HTTP 客户端上手动注册,如下所示:
public ArgumentValueClient(HttpGraphQlClient graphQlClient) {
JsonMapper jsonMapper = JsonMapper.builder().addModule(new GraphQlJacksonModule()).build();
JacksonJsonEncoder jsonEncoder = new JacksonJsonEncoder(jsonMapper);
WebClient webClient = WebClient.builder()
.baseUrl("https://example.com/graphql")
.codecs((codecs) -> codecs.defaultCodecs().jacksonJsonEncoder(jsonEncoder))
.build();
this.graphQlClient = HttpGraphQlClient.create(webClient);
}
这GraphQlJacksonModule
可以通过将其作为 bean 贡献在 Spring Boot 应用程序中全局注册:
@Configuration
public class GraphQlJsonConfiguration {
@Bean
public GraphQlJacksonModule graphQLModule() {
return new GraphQlJacksonModule();
}
}
Jackson 2.x 支持也可用于GraphQlJackson2Module . |
DGS Codegen
作为提供作(如突变、查询或订阅)的替代方法 text 中,您可以使用 DGS Codegen 库来 生成客户端 API 类,允许您使用 Fluent API 来定义请求。
Spring for GraphQL 提供了 DgsGraphQlClient,它包装了任何GraphQlClient
并帮助使用生成的客户端准备请求
API 类。
例如,给定以下架构:
type Query {
books: [Book]
}
type Book {
id: ID
name: String
}
您可以按如下方式执行请求:
HttpGraphQlClient client = HttpGraphQlClient.create(WebClient.create("https://example.org/graphql"));
DgsGraphQlClient dgsClient = DgsGraphQlClient.create(client); (1)
List<Book> books = dgsClient.request(BookByIdGraphQLQuery.newRequest().id("42").build()) (2)
.projection(new BooksProjectionRoot<>().id().name()) (3)
.retrieveSync("books")
.toEntityList(Book.class);
1 | 创造DgsGraphQlClient 通过将任何GraphQlClient . |
2 | 指定请求的作。 |
3 | 定义选择集。 |
这DgsGraphQlClient
还支持通过链接进行多个查询query()
调用:
HttpGraphQlClient client = HttpGraphQlClient.create(WebClient.create("https://example.org/graphql"));
DgsGraphQlClient dgsClient = DgsGraphQlClient.create(client); (1)
ClientGraphQlResponse response = dgsClient
.request(BookByIdGraphQLQuery.newRequest().id("42").build()) (2)
.queryAlias("firstBook") (3)
.projection(new BooksProjectionRoot<>().id().name())
.request(BookByIdGraphQLQuery.newRequest().id("53").build()) (4)
.queryAlias("secondBook")
.projection(new BooksProjectionRoot<>().id().name())
.executeSync(); (5)
Book firstBook = response.field("firstBook").toEntity(Book.class); (6)
Book secondBook = response.field("secondBook").toEntity(Book.class);
1 | 创造DgsGraphQlClient 通过将任何GraphQlClient . |
2 | 指定第一个请求的作。 |
3 | 当发送多个请求时,我们需要为每个请求指定一个别名 |
4 | 指定第二个请求的作。 |
5 | 获取完整回复 |
6 | 获取具有配置别名的相关文档部件 |