此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring GraphQL 1.4.1spring-doc.cadn.net.cn

客户端

Spring for GraphQL 包括通过 HTTP 执行 GraphQL 请求的客户端支持, WebSocket 和 RSocket。spring-doc.cadn.net.cn

GraphQlClient

GraphQlClient为 GraphQL 请求定义一个独立于底层的通用工作流 transport,因此无论使用哪种传输,执行请求的方式都是相同的。spring-doc.cadn.net.cn

以下特定于传输GraphQlClient扩展可用:spring-doc.cadn.net.cn

每个定义了一个Builder与运输相关的选项。所有构建器都扩展 从通用的基本 GraphQlClientBuilder选项适用于所有运输。spring-doc.cadn.net.cn

一次GraphQlClient已构建,您可以开始提出请求spring-doc.cadn.net.cn

通常,请求的 GraphQL作以文本形式提供。或者,您 可以通过 DgsGraphQlClient 使用 DGS Codegen 客户端 API 类,该类可以包装任何 以上GraphQlClient扩展。spring-doc.cadn.net.cn

HTTP 同步

HttpSyncGraphQlClient使用 RestClient 通过阻塞传输合约和 拦截 器。spring-doc.cadn.net.cn

RestClient restClient = RestClient.create("https://spring.io/graphql");
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);

一次HttpSyncGraphQlClient创建时,您可以开始使用相同的 API 执行请求,而不受底层 运输。如果您需要更改任何特定于传输的详细信息,请使用mutate()在 现存HttpSyncGraphQlClient要使用自定义设置创建新实例,请执行以下作:spring-doc.cadn.net.cn

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 执行 通过非阻塞传输合约和 拦截 器。spring-doc.cadn.net.cn

WebClient webClient = WebClient.create("https://spring.io/graphql");
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);

一次HttpGraphQlClient创建时,您可以开始使用相同的 API 执行请求,而不受底层 运输。如果您需要更改任何特定于传输的详细信息,请使用mutate()在 现存HttpGraphQlClient要使用自定义设置创建新实例,请执行以下作:spring-doc.cadn.net.cn

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 构建的,您可以按如下方式创建它:spring-doc.cadn.net.cn

String url = "wss://spring.io/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();

WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build();

HttpGraphQlClientWebSocketGraphQlClient以连接为导向, 这意味着它需要在发出任何请求之前建立连接。开始时 为了发出请求,连接是透明地建立的。或者,使用 客户的start()方法在任何请求之前显式建立连接。spring-doc.cadn.net.cn

除了以连接为导向之外,WebSocketGraphQlClient也是多路复用的。 它为所有请求维护一个单一的共享连接。如果连接丢失, 它在下一个请求时重新建立,或者如果start()再次调用。您还可以 使用客户端的stop()方法,关闭 connection,并拒绝新请求。spring-doc.cadn.net.cn

使用单个WebSocketGraphQlClient实例,以便拥有 对该服务器的所有请求的单个共享连接。每个客户端实例 建立自己的连接,这通常不是单个服务器的意图。

一次WebSocketGraphQlClient创建时,您可以开始使用相同的 API 执行请求,而不受底层 运输。如果您需要更改任何特定于传输的详细信息,请使用mutate()在 现存WebSocketGraphQlClient要使用自定义设置创建新实例,请执行以下作:spring-doc.cadn.net.cn

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消息以保持连接 当没有发送或接收其他消息时处于活动状态。您可以按如下方式启用它:spring-doc.cadn.net.cn

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"在连接开始时。spring-doc.cadn.net.cn

对于特定于 WebSocket 传输的拦截,您可以创建WebSocketGraphQlClientInterceptor:spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

RS袜子

RSocketGraphQlClient使用 RSocketRequester 通过 RSocket 请求执行 GraphQL 请求。spring-doc.cadn.net.cn

URI uri = URI.create("wss://localhost:8080/rsocket");
WebsocketClientTransport transport = WebsocketClientTransport.create(uri);

RSocketGraphQlClient client = RSocketGraphQlClient.builder()
		.clientTransport(transport)
		.build();

HttpGraphQlClientRSocketGraphQlClient以连接为导向, 这意味着它需要在发出任何请求之前建立一个会话。开始时 为了发出请求,会话是透明地建立的。或者,使用 客户的start()方法在任何请求之前显式建立会话。spring-doc.cadn.net.cn

RSocketGraphQlClient也是多路复用的。它维护一个单独的共享会话 所有请求。如果会话丢失,则在下一个请求时重新建立会话,或者如果start()再次调用。您还可以使用客户端的stop()取消的方法 in-progress 请求,关闭会话,并拒绝新请求。spring-doc.cadn.net.cn

使用单个RSocketGraphQlClient实例,以便拥有 对该服务器的所有请求的单个共享会话。每个客户端实例 建立自己的连接,这通常不是单个服务器的意图。

一次RSocketGraphQlClient创建时,您可以开始使用相同的 API 执行请求,而不受底层 运输。spring-doc.cadn.net.cn

架构工人

GraphQlClient定义父级BaseBuilder具有 所有扩展的构建者。目前,它允许您配置:spring-doc.cadn.net.cn

BaseBuilder进一步扩展了以下内容:spring-doc.cadn.net.cn

请求

一旦你有一个GraphQlClient,您可以开始通过检索执行方法执行请求。spring-doc.cadn.net.cn

取回

以下内容检索并解码查询的数据:spring-doc.cadn.net.cn

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可以是文字,也可以是通过代码生成的 生成的请求对象。您还可以在文件中定义文档,并使用文档源按文件名重新定位它们。spring-doc.cadn.net.cn

该路径相对于“data”键,并使用简单的点 (“.”) 分隔表示法 对于具有列表元素可选数组索引的嵌套字段,例如"project.name""project.releases[0].version".spring-doc.cadn.net.cn

解码可能导致FieldAccessException如果给定的路径不存在,或者 字段值为null并且有一个错误。FieldAccessException提供对 response 和字段:spring-doc.cadn.net.cn

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而是被抛出。spring-doc.cadn.net.cn

执行

检索只是从单个路径解码的快捷方式 响应图。要获得更多控制,请使用execute方法并处理响应:spring-doc.cadn.net.cn

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可以在局部变量中定义或 常量,或者它可以通过代码生成的请求对象生成。spring-doc.cadn.net.cn

您还可以创建带有扩展名的文档文件.graphql.gql"graphql-documents/"并按文件名引用它们。spring-doc.cadn.net.cn

例如,给定一个名为projectReleases.graphqlsrc/main/resources/graphql-documents,内容:spring-doc.cadn.net.cn

src/main/resources/graphql-documents/projectReleases.graphql
query projectReleases($slug: ID!) {
	project(slug: $slug) {
		name
		releases {
			version
		}
	}
}

然后,您可以:spring-doc.cadn.net.cn

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 查询文件。spring-doc.cadn.net.cn

您可以使用GraphQlClient 构建器来自定义DocumentSource用于按名称加载文档。spring-doc.cadn.net.cn

订阅请求

订阅请求需要能够流式传输数据的客户端传输。 您需要创建一个GraphQlClient支持这一点:spring-doc.cadn.net.cn

取回

要启动订阅流,请使用retrieveSubscription这类似于对单个响应的检索,但返回 响应,每个响应都解码为一些数据:spring-doc.cadn.net.cn

Flux<String> greetingFlux = client.document("subscription { greetings }")
		.retrieveSubscription("greeting")
		.toEntity(String.class);

Flux可以以SubscriptionErrorException如果订阅从 服务器端显示“错误”消息。该异常提供对 GraphQL 错误的访问 从“错误”消息中解码。spring-doc.cadn.net.cn

Flux可以以GraphQlTransportExceptionWebSocketDisconnectedException如果底层连接已关闭或丢失。在那 案例,您可以使用retry运算符以重新启动订阅。spring-doc.cadn.net.cn

要从客户端结束订阅,请Flux必须取消,反过来 WebSocket 传输向服务器发送“完整”消息。如何取消Flux取决于它的使用方式。一些运算符,例如taketimeout他们自己 取消Flux. 如果您订阅了Flux使用Subscriber,您可以获得一个引用Subscription并通过它取消。 这onSubscribe运算符还提供对Subscription.spring-doc.cadn.net.cn

执行

检索只是从每个响应映射中的单个路径解码的快捷方式。要获得更多控制,请使用executeSubscription方法并处理每个 直接回应:spring-doc.cadn.net.cn

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要拦截通过客户端的所有请求:spring-doc.cadn.net.cn

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要拦截通过客户端的所有请求:spring-doc.cadn.net.cn

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);
	}

}

创建拦截器后,通过客户端构建器进行注册。例如:spring-doc.cadn.net.cn

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或者根本没有相应地改变。spring-doc.cadn.net.cn

类似于ArgumentValue<T> support in controllers, 我们可以用ArgumentValue<T>或者在客户端的类属性级别使用它。 给定一个ProjectInput类,例如:spring-doc.cadn.net.cn

import org.springframework.graphql.data.ArgumentValue;

public record ProjectInput(String id, ArgumentValue<String> name) {

}

我们可以使用客户端发送变更请求:spring-doc.cadn.net.cn

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 客户端上手动注册,如下所示:spring-doc.cadn.net.cn

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 应用程序中全局注册:spring-doc.cadn.net.cn

@Configuration
public class GraphQlJsonConfiguration {

	@Bean
	public GraphQlJacksonModule graphQLModule() {
		return new GraphQlJacksonModule();
	}

}
Jackson 2.x 支持也可用于GraphQlJackson2Module.

DGS Codegen

作为提供作(如突变、查询或订阅)的替代方法 text 中,您可以使用 DGS Codegen 库来 生成客户端 API 类,允许您使用 Fluent API 来定义请求。spring-doc.cadn.net.cn

Spring for GraphQL 提供了 DgsGraphQlClient,它包装了任何GraphQlClient并帮助使用生成的客户端准备请求 API 类。spring-doc.cadn.net.cn

例如,给定以下架构:spring-doc.cadn.net.cn

type Query {
    books: [Book]
}

type Book {
    id: ID
    name: String
}

您可以按如下方式执行请求:spring-doc.cadn.net.cn

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()调用:spring-doc.cadn.net.cn

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 获取具有配置别名的相关文档部件