Spring for GraphQL 包括客户端支持通过 HTTP 执行 GraphQL 请求, WebSocket 和 RSocket。Spring中文文档

GraphQlClient

GraphQlClient为独立于底层的 GraphQL 请求定义通用工作流 传输,因此无论使用哪种传输方式,执行请求的方式都是相同的。Spring中文文档

提供以下特定于传输的扩展:GraphQlClientSpring中文文档

每个定义一个与传输相关的选项。所有构建器扩展 来自通用的基本 GraphQlClient Builder,其中包含适用于所有传输的选项。BuilderSpring中文文档

一旦构建完成,您就可以开始提出请求了。GraphQlClientSpring中文文档

通常,请求的 GraphQL 操作以文本形式提供。或者,你 可以通过 DgsGraphQlClient 使用 DGS Codegen 客户端 API 类,它可以包装任何 上面的扩展。GraphQlClientSpring中文文档

HTTP 同步

HttpSyncGraphQlClient使用 RestClient 通过阻塞传输协定和 拦截 器。Spring中文文档

RestClient restClient = ... ;
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);

创建后,您可以开始使用相同的 API 执行请求,而与基础无关 运输。如果您需要更改任何传输特定的详细信息,请在 existing 创建具有自定义设置的新实例:HttpSyncGraphQlClientmutate()HttpSyncGraphQlClientSpring中文文档

   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 执行 GraphQL 通过非阻塞传输合约和 拦截 器。Spring中文文档

WebClient webClient = ... ;
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);

创建后,您可以开始使用相同的 API 执行请求,而与基础无关 运输。如果您需要更改任何传输特定的详细信息,请在 existing 创建具有自定义设置的新实例:HttpGraphQlClientmutate()HttpGraphQlClientSpring中文文档

   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...

网络套接字

WebSocketGraphQlClient通过共享 WebSocket 连接执行 GraphQL 请求。 它是使用 Spring WebFlux 中的 WebSocketClient 构建的,您可以按如下方式创建它:Spring中文文档

String url = "wss://localhost:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();

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

与 相反,是面向连接的, 这意味着它需要在发出任何请求之前建立连接。当你开始的时候 要发出请求,连接是透明的。或者,使用 客户端在任何请求之前显式建立连接的方法。HttpGraphQlClientWebSocketGraphQlClientstart()Spring中文文档

除了面向连接外,还具有多路复用功能。 它为所有请求维护一个单一的共享连接。如果连接丢失, 它将在下一个请求或再次调用时重新建立。您还可以 使用客户端的方法取消正在进行的请求,关闭 连接,并拒绝新请求。WebSocketGraphQlClientstart()stop()Spring中文文档

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

创建后,您可以开始使用相同的 API 执行请求,而与基础无关 运输。如果您需要更改任何传输特定的详细信息,请在 existing 创建具有自定义设置的新实例:WebSocketGraphQlClientmutate()WebSocketGraphQlClientSpring中文文档

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

URI url = ... ;
WebSocketClient client = ... ;

WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
		.keepAlive(Duration.ofSeconds(30))
		.build();

拦截 器

GraphQL over WebSocket 协议除了执行 请求。例如,客户端发送,服务器在连接开始时响应。"connection_init""connection_ack"Spring中文文档

对于特定于 WebSocket 传输的拦截,您可以创建一个:WebSocketGraphQlClientInterceptorSpring中文文档

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 请求,但请注意 最多可以是一个类型的拦截器。GraphQlClientInterceptorWebSocketGraphQlClientInterceptorSpring中文文档

RSocket

RSocketGraphQlClient使用 RSocketRequester 对 RSocket 请求执行 GraphQL 请求。Spring中文文档

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

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

与 相反,是面向连接的, 这意味着它需要在发出任何请求之前建立一个会话。当你开始的时候 为了提出请求,会话是透明地建立的。或者,使用 客户端在任何请求之前显式建立会话的方法。HttpGraphQlClientRSocketGraphQlClientstart()Spring中文文档

RSocketGraphQlClient也是多路复用的。它维护一个共享会话 所有请求。如果会话丢失,则在下一个请求或再次调用时重新建立会话。您也可以使用客户端的取消方法 正在进行的请求,关闭会话,并拒绝新请求。start()stop()Spring中文文档

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

创建后,您可以开始使用相同的 API 执行请求,而与基础无关 运输。RSocketGraphQlClientSpring中文文档

建筑工人

GraphQlClient定义一个父级,其中包含 所有扩展的构建者。目前,它允许您配置:BaseBuilderSpring中文文档

BaseBuilder进一步扩展如下:Spring中文文档

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

请求

拥有 GraphQlClient 后,您可以开始通过 retrieveexecute 方法执行请求。Spring中文文档

取回

下面检索和解码查询的数据:Spring中文文档

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 对目标类型路径处的数据进行解码。

输入文档可以是文本,也可以是通过代码生成的 生成的请求对象。您还可以在文件中定义文档,并使用文档源按文件名重新指定文档。StringSpring中文文档

路径相对于“data”键,并使用简单的点 (“.”) 分隔表示法 对于具有列表元素可选数组索引的嵌套字段,例如 或。"project.name""project.releases[0].version"Spring中文文档

如果给定路径不存在,则解码可能会导致 字段值为 和 有错误。 提供对 响应和字段:FieldAccessExceptionnullFieldAccessExceptionSpring中文文档

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 只是从 响应映射。若要进行更多控制,请使用以下方法并处理响应:executeSpring中文文档

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 在给定路径上解码数据

文档源

请求的文档是可以在局部变量中定义的文档,或者 常量,也可以通过代码生成的请求对象来生成。StringSpring中文文档

您还可以在类路径上创建扩展名或以下的文档文件,并按文件名引用它们。.graphql.gql"graphql-documents/"Spring中文文档

例如,给定一个名为 的文件,其中包含 的内容:projectReleases.graphqlsrc/main/resources/graphql-documentsSpring中文文档

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

然后,您可以:Spring中文文档

Project project = graphQlClient.documentName("projectReleases") (1)
		.variable("slug", "spring-framework") (2)
		.retrieveSync()
		.toEntity(Project.class);
1 从“projectReleases.graphql”加载文档
2 提供变量值。

此方法也适用于加载查询的片段。 片段是可重用的字段选择集,可避免在请求文档中重复。 例如,我们可以在多个查询中使用一个片段:…​releasesSpring中文文档

src/main/resources/graphql-documents/projectReleases.graphql
query frameworkReleases {
	project(slug: "spring-framework") {
		name
		...releases
	}
}
query graphqlReleases {
       project(slug: "spring-graphql") {
           name
           ...releases
       }
   }

此片段可以在单独的文件中定义以供重用:Spring中文文档

src/main/resources/graphql-documents/releases.graphql
fragment releases on Project {
   	releases {
           version
       }
   }

然后,您可以随查询文档发送此片段:Spring中文文档

Project project = graphQlClient.documentName("projectReleases") (1)
		.fragmentName("releases") (2)
		.retrieveSync()
		.toEntity(Project.class);
1 从“projectReleases.graphql”加载文档
2 从“releases.graphql”加载片段并将其附加到文档中

IntelliJ 的“JS GraphQL”插件支持具有代码完成的 GraphQL 查询文件。Spring中文文档

您可以使用构建器自定义按名称加载文档。GraphQlClientDocumentSourceSpring中文文档

1 要执行的操作。
2 响应映射中要解码的“data”键下的路径。
3 对目标类型路径处的数据进行解码。
1 响应没有数据,只有错误
2 具有关联错误的字段null
3 字段设置为nullDataFetcher
4 在给定路径上解码数据
1 从“projectReleases.graphql”加载文档
2 提供变量值。
1 从“projectReleases.graphql”加载文档
2 从“releases.graphql”加载片段并将其附加到文档中

订阅请求

订阅请求需要能够流式处理数据的客户端传输。 您将需要创建一个支持此功能:GraphQlClientSpring中文文档

取回

若要启动订阅流,请使用 which 类似于检索单个响应,但返回 响应,每个响应都解码为一些数据:retrieveSubscriptionSpring中文文档

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

如果订阅从 服务器端显示“错误”消息。该异常提供对 GraphQL 错误的访问 从“错误”消息解码。FluxSubscriptionErrorExceptionSpring中文文档

如果基础连接关闭或丢失,则可能会终止。在那 如果您可以使用 Operator 重新启动订阅。FluxGraphQlTransportExceptionWebSocketDisconnectedExceptionretrySpring中文文档

若要从客户端结束订阅,必须取消,然后依次取消 WebSocket 传输向服务器发送“完整”消息。如何取消取决于它的使用方式。一些运营商,例如 or 他们自己 取消 .如果你订阅了 with a ,你可以得到一个 引用并通过它取消。运营商还 提供对 .FluxFluxtaketimeoutFluxFluxSubscriberSubscriptiononSubscribeSubscriptionSpring中文文档

执行

Retrieve 只是从每个路径中的单个路径解码的快捷方式 响应映射。为了获得更多控制,请使用该方法并处理每个 直接响应:executeSubscriptionSpring中文文档

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.SyncBuilderSyncGraphQlClientInterceptorSpring中文文档

static class MyInterceptor implements SyncGraphQlClientInterceptor {

	@Override
	public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) {
		// ...
		return chain.next(request);
	}
}

对于使用 创建的非阻塞传输,您可以创建一个来拦截通过客户端的所有请求:GraphQlClient.BuilderGraphQlClientInterceptorSpring中文文档

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

}

创建拦截器后,通过客户端构建器注册它。例如:Spring中文文档

URI url = ... ;
WebSocketClient client = ... ;

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

DGS 编码

作为提供更改、查询或订阅等操作的替代方法 text,您可以使用 DGS Codegen 库来 生成允许您使用 Fluent API 来定义请求的客户端 API 类。Spring中文文档

Spring for GraphQL 提供了 DgsGraphQlClient,它可以包装任何客户端并帮助使用生成的客户端准备请求 API 类。GraphQlClientSpring中文文档

例如,给定以下架构:Spring中文文档

type Query {
    books: [Book]
}

type Book {
    id: ID
    name: String
}

您可以按如下方式执行请求:Spring中文文档

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 - 定义选择集。