Spring for GraphQL入门教程(二)

时间:2022-12-06 17:57:48

Spring for GraphQL入门教程(二)

5. 带注释的控制者

Spring for GraphQL 提供了一个基于注释的编程模型,其中组件使用注释来声明具有灵活方法签名的处理程序方法 获取特定 GraphQL 字段的数据。例如:​​@Controller​

@Controller
public class GreetingController {

@QueryMapping
public String hello() {
return "Hello, world!";
}

}

将此方法绑定到查询,即查询类型下的字段。

如果未在批注中声明,则根据方法名称确定查询。

Spring for GraphQL 用于将上述处理程序方法注册为名为 “hello” 的查询。​​RuntimeWiring.Builder​​​​graphql.schema.DataFetcher​

5.1. 声明

您可以将豆类定义为标准的春豆定义。刻板印象允许自动检测,与弹簧常规对齐 支持类路径上的检测和类 自动注册它们的 Bean 定义。它还充当注释的刻板印象 类,指示它在 GraphQL 应用程序中作为数据获取组件的角色。​​@Controller​​​​@Controller​​​​@Controller​​​​@Component​

​AnnotatedControllerConfigurer​​检测豆类并注册其 带注释的处理程序方法 ass via。这是一个 可以添加其中的实现。 Spring 引导启动器自动声明为 bean 并添加所有豆子,使 支持注释,请参阅GraphQL 运行时连接部分 在引导启动器文档中。​​@Controller​​​​DataFetcher​​​​RuntimeWiring.Builder​​​​RuntimeWiringConfigurer​​​​GraphQlSource.Builder​​​​AnnotatedControllerConfigurer​​​​RuntimeWiringConfigurer​​​​GraphQlSource.Builder​​​​DataFetcher​

5.2. ​​@SchemaMapping​

注释将处理程序方法映射到 GraphQL 架构中的字段 并声明它是该字段。注释可以指定 父类型名称和字段名称:​​@SchemaMapping​​​​DataFetcher​

@Controller
public class BookController {

@SchemaMapping(typeName="Book", field="author")
public Author getAuthor(Book book) {
// ...
}
}

注释也可以省略这些属性,在这种情况下, 字段名称默认为方法名称,而类型名称默认为简单类 注入到方法中的源/父对象的名称。例如,下面 默认键入“书籍”和字段“作者”:​​@SchemaMapping​

@Controller
public class BookController {

@SchemaMapping
public Author author(Book book) {
// ...
}
}

可以在类级别声明注释以指定默认值 类中所有处理程序方法的类型名称。​​@SchemaMapping​

@Controller
@SchemaMapping(typeName="Book")
public class BookController {

// @SchemaMapping methods for fields of the "Book" type

}

​@QueryMapping​​,,和是元注释 本身带有注释,并将“字体名称”预设为 、、 或分别。实际上,这些是快捷方式注释 分别用于“查询”、“变更”和“订阅类型”下的字段。例如:​​@MutationMapping​​​​@SubscriptionMapping​​​​@SchemaMapping​​​​Query​​​​Mutation​​​​Subscription​

@Controller
public class BookController {

@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}

@MutationMapping
public Book addBook(@Argument BookInput bookInput) {
// ...
}

@SubscriptionMapping
public Flux<Book> newPublications() {
// ...
}
}

​@SchemaMapping​​处理程序方法具有灵活的签名,可以从一系列 方法参数和返回值。

5.2.1. 方法签名

架构映射处理程序方法可以具有以下任何方法参数:

方法参数

描述

​@Argument​

用于访问绑定到更高级别类型化对象的命名字段参数。 看@Argument。

​@Argument Map<String, Object>​

对于访问参数的原始映射,其中没有属性。​​@Argument​​​​name​

​ArgumentValue​

用于访问绑定到更高级别、键入的 Object 的命名字段参数 带有一个标志,用于指示输入参数是省略还是设置为。 请参阅参数值​。​​null​

​@Arguments​

用于访问绑定到更高级别的类型化对象的所有字段参数。 看@Arguments。

​@Arguments Map<String, Object>​

用于访问参数的原始映射。

​@ProjectedPayload​​接口

用于通过项目界面访问字段参数。 请参阅@ProjectedPayload界面。

“来源”

用于访问字段的源(即父/容器)实例。 请参阅​​来源​​。

​DataLoader​

为了访问。 请参阅数据加载器​。​​DataLoader​​​​DataLoaderRegistry​

​@ContextValue​

用于从主界面访问属性。​​GraphQLContext​​​​DataFetchingEnvironment​

​@LocalContextValue​

用于从本地访问属性。​​GraphQLContext​​​​DataFetchingEnvironment​

​GraphQLContext​

用于从上下文访问。​​DataFetchingEnvironment​

​java.security.Principal​

从 Spring 安全性上下文(如果可用)获取。

​@AuthenticationPrincipal​

用于从 Spring 安全性上下文访问。​​Authentication#getPrincipal()​

​DataFetchingFieldSelectionSet​

用于访问用于查询的选择集。​​DataFetchingEnvironment​

​Locale​​​, ​​Optional<Locale>​

用于访问从。​​Locale​​​​DataFetchingEnvironment​

​DataFetchingEnvironment​

用于直接访问底层证券。​​DataFetchingEnvironment​

架构映射处理程序方法可以返回:

  • 任何类型的解析值。
  • ​Mono​​和异步值。支持控制器方法和 ReactiveDataFetcher 中描述的 anyas。FluxDataFetcher
  • ​java.util.concurrent.Callable​​异步生成值。 为此,必须配置一个。AnnotatedControllerConfigurerExecutor

5.2.2. ​​@Argument​

在 GraphQL Java 中,提供对特定于字段的映射的访问 参数值。这些值可以是简单的标量值(例如字符串、长整型)、aof 更复杂输入的值或 AOF 值。​​DataFetchingEnvironment​​​​Map​​​​List​

使用注释将参数绑定到目标对象,并且 注入到处理程序方法中。通过将参数值映射到 预期方法参数类型的主数据构造函数,或使用默认值 构造函数,以创建对象,然后将参数值映射到其属性。这是 递归重复,使用所有嵌套参数值并创建嵌套目标对象 因此。例如:​​@Argument​

@Controller
public class BookController {

@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}

@MutationMapping
public Book addBook(@Argument BookInput bookInput) {
// ...
}
}

默认情况下,如果方法参数名称可用(需要编译器 标志与 Java 8+ 或编译器中的调试信息),它用于查找参数。 如果需要,您可以通过注释自定义名称,例如​​-parameters​​​​@Argument("bookInput")​

注释没有“必需”标志,也没有选项 指定默认值。这两者都可以在 GraphQL 模式级别指定,并且 由 GraphQL Java 强制执行。​​@Argument​

如果绑定失败,则引发绑定问题累积为字段 错误,其中每个错误都是出现问题的参数路径。​​BindException​​​​field​

您可以使用参数,获得原始地图 所有参数值。不得设置 name 属性 on。​​@Argument​​​​Map<String, Object>​​​​@Argument​

5.2.3. ​​ArgumentValue​

默认情况下,GraphQL 中的输入参数可为空且可选,这意味着参数 可以设置为文字,也可以根本不提供。这种区别对于 带有突变的部分更新,其中基础数据也可能是,设置为或根本不相应地更改。使用时@Argument没有办法进行这样的区分,因为在这两种情况下您都会得到一个空。​​null​​​​null​​​​null​​​​Optional​

如果你不想知道是否根本没有提供值,你可以声明一个method参数,它是结果值的简单容器, 以及一个标志,用于指示是否完全省略了输入参数。你 可以使用这个代替,在这种情况下,参数名称由 方法参数名称,或与一起指定参数名称。​​ArgumentValue​​​​@Argument​​​​@Argument​

例如:

@Controller
public class BookController {

@MutationMapping
public void addBook(ArgumentValue<BookInput> bookInput) {
if (!bookInput.isOmitted()) {
BookInput value = bookInput.value();
// ...
}
}
}

​ArgumentValue​​也支持作为 anMethod 参数的对象结构中的字段,通过构造函数参数或通过 setter 初始化,包括 作为嵌套在*对象下任何级别的对象的字段。​​@Argument​

5.2.4. ​​@Arguments​

如果要将完整的参数映射绑定到单个参数映射上,请使用注释 目标对象,相反,它绑定特定的命名参数。​​@Arguments​​​​@Argument​

例如,使用参数“bookInput”的值 初始化,同时使用完整的参数映射,并在其中 在这种情况下,*参数绑定到属性。​​@Argument BookInput bookInput​​​​BookInput​​​​@Arguments​​​​BookInput​

您可以使用参数,获得原始地图 所有参数值。​​@Arguments​​​​Map<String, Object>​

5.2.5.接口​​@ProjectedPayload​

作为将完整对象与@Argument一起使用的替代方法, 您还可以使用投影接口通过 定义明确,最小的接口。当 Spring 数据位于类路径上时,参数投影由Spring Data 的接口投影提供。

要利用这一点,请创建一个注释为 并声明的接口 它作为控制器方法参数。如果参数用 注释, 它适用于 Themap 中的单个参数。当声明没有时,投影适用于 完整的参数映射。​​@ProjectedPayload​​​​@Argument​​​​DataFetchingEnvironment.getArguments()​​​​@Argument​

例如:

@Controller
public class BookController {

@QueryMapping
public Book bookById(BookIdProjection bookId) {
// ...
}

@MutationMapping
public Book addBook(@Argument BookInputProjection bookInput) {
// ...
}
}

@ProjectedPayload
interface BookIdProjection {

Long getId();
}

@ProjectedPayload
interface BookInputProjection {

String getName();

@Value("#{target.author + ' ' + target.name}")
String getAuthorAndName();
}

5.2.6. 来源

在 GraphQL Java 中,提供对源代码的访问(即 父/容器)字段实例。要访问它,只需声明一个方法参数 预期的目标类型。​​DataFetchingEnvironment​

@Controller
public class BookController {

@SchemaMapping
public Author author(Book book) {
// ...
}
}

源方法参数还有助于确定映射的类型名称。 如果 Java 类的简单名称与 GraphQL 类型匹配,则无需 在注释中显式指定类型名称。​​@SchemaMapping​


A@BatchMapping处理程序方法可以批量加载查询的所有作者, 给定源/父书籍对象的列表。


5.2.7. ​​DataLoader​

当您为实体注册批处理加载函数时,如批处理加载中所述,您可以通过声明 类型的方法参数并使用它来加载实体:​​DataLoader​​​​DataLoader​

@Controller
public class BookController {

public BookController(BatchLoaderRegistry registry) {
registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
// return Map<Long, Author>
});
}

@SchemaMapping
public CompletableFuture<Author> author(Book book, DataLoader<Long, Author> loader) {
return loader.load(book.getAuthorId());
}

}

默认情况下,使用值类型的完整类名(例如 类名 for) 作为注册的密钥,因此只需声明 具有泛型类型的方法参数提供了足够的信息 以在 中找到它。作为回退,方法参数 解析器还将尝试将方法参数名称作为键,但通常不应 是必要的。​​BatchLoaderRegistry​​​​Author​​​​DataLoader​​​​DataLoaderRegistry​​​​DataLoader​

请注意,对于许多加载相关实体的情况,其中简单的 委托给 A,您可以使用 A@BatchMapping方法减少样板,如下一节所述。​​@SchemaMapping​​​​DataLoader​

5.2.8. 验证

找到 abean 后,启用对带注释的控制器方法的Bean 验证支持。通常,Bean 属于类型。​​javax.validation.Validator​​​​AnnotatedControllerConfigurer​​​​LocalValidatorFactoryBean​

Bean 验证允许您声明对类型的约束:

public class BookInput {

@NotNull
private String title;

@NotNull
@Size(max=13)
private String isbn;
}

然后,您可以注释控制器方法参数以在验证之前对其进行验证 方法调用:​​@Valid​

@Controller
public class BookController {

@MutationMapping
public Book addBook(@Argument @Valid BookInput bookInput) {
// ...
}
}

如果在验证过程中发生错误,则会引发 ais 。 可以使用异常解析链来决定如何将其呈现给客户端 通过将其转换为要包含在 GraphQL 响应中的错误。​​ConstraintViolationException​

除此之外,您还可以使用Spring的允许 指定验证组。​​@Valid​​​​@Validated​

Bean 验证对于@Argument、@Arguments 和@ProjectedPayload方法参数很有用,但更普遍地适用于任何方法参数。

验证和 Kotlin 协程


休眠验证器与 Kotlin 协程方法不兼容,并且在以下情况下失败 反省其方法参数。请参阅spring-projects/spring-graphql#344 (comment)以获取相关问题的链接和建议的解决方法。


5.3. ​​@BatchMapping​

批量加载通过使用 anto 延迟单个实体实例的加载来解决 N+1 选择问题,因此它们 可以一起加载。例如:​​org.dataloader.DataLoader​

@Controller
public class BookController {

public BookController(BatchLoaderRegistry registry) {
registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
// return Map<Long, Author>
});
}

@SchemaMapping
public CompletableFuture<Author> author(Book book, DataLoader<Long, Author> loader) {
return loader.load(book.getAuthorId());
}

}

对于加载关联实体的直接情况,如上所示,该方法只不过是委托给。这是 可以用方法避免的样板。例如:​​@SchemaMapping​​​​DataLoader​​​​@BatchMapping​

@Controller
public class BookController {

@BatchMapping
public Mono<Map<Book, Author>> author(List<Book> books) {
// ...
}
}

上面成为其中键实例中的批量加载函数,加载的值是它们的作者。此外,ais 还透明地绑定到该类型的字段,其中 只是委托给 for 作者,给定其源/父实例。​​BatchLoaderRegistry​​​​Book​​​​DataFetcher​​​​author​​​​Book​​​​DataLoader​​​​Book​


要用作唯一键,必须实现和。​​Book​​​​hashcode​​​​equals​


默认情况下,字段名称默认为方法名称,而类型名称默认为 输入元素类型的简单类名。两者都可以通过以下方式定制 批注属性。类型名称也可以从类级别继承。​​List​​​​@SchemaMapping​

5.3.1. 方法签名

批处理映射方法支持以下参数:

方法参数

描述

​List<K>​

源/父对象。

​java.security.Principal​

从 Spring 安全性上下文(如果可用)获取。

​@ContextValue​

为了访问来自 theof 的值, 这与来自的上下文相同。​​GraphQLContext​​​​BatchLoaderEnvironment​​​​DataFetchingEnvironment​

​GraphQLContext​

要从上下文访问, 这与来自的上下文相同。​​BatchLoaderEnvironment​​​​DataFetchingEnvironment​

​BatchLoaderEnvironment​

在 GraphQL Java 中可用的环境。​​org.dataloader.BatchLoaderWithContext​

批处理映射方法可以返回:

返回类型

描述

​Mono<Map<K,V>>​

以父对象为键,批量加载的对象作为值的映射。

​Flux<V>​

批处理加载对象的序列,其顺序必须与源/父对象相同 传递给方法的对象。

​Map<K,V>​​​, ​​Collection<V>​

命令式变体,例如无需进行远程调用。

​Callable<Map<K,V>>​​​, ​​Callable<Collection<V>>​

要异步调用的命令性变体。为此,必须配置一个。​​AnnotatedControllerConfigurer​​​​Executor​

6. 安全

WebGraphQL 端点的路径可以使用 HTTP 进行保护 URL 安全性,以确保只有经过身份验证的用户才能访问它。这不, 但是,区分此类共享端点上的不同 GraphQL 请求 单个网址。

要应用更细粒度的安全性,请添加 Spring 安全性注释,例如在获取特定部分时涉及的 asorto 服务方法 GraphQL 响应。这应该有效,因为上下文传播旨在使 安全性和其他上下文,在数据获取级别可用。@PreAuthorize@Secured

Spring for GraphQL 存储库包含Spring MVC和WebFlux 的示例。

7. 客户

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

7.1. ​​GraphQlClient​

​GraphQlClient​​是一个合约,它为 GraphQL 请求声明了一个通用工作流,即 独立于基础传输。这意味着请求使用相同的 API 执行 无论基础传输是什么,以及任何特定于传输的内容都配置为 构建时间。

要创建您需要以下扩展之一:​​GraphQlClient​

  • HttpGraphQlClient
  • WebSocketGraphQlClient
  • RSocketGraphQlClient

每个都定义了与传输相关的选项。所有构建器扩展 来自一个通用的、基本的 GraphQlClientBuilder,带有选项 与所有扩展相关。​​Builder​

一旦你有了,你就可以开始提出请求了。​​GraphQlClient​

7.1.1. HTTP

​HttpGraphQlClient​​使用网络客户端执行 通过 HTTP 的 GraphQL 请求。

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

创建后,您可以开始使用相同的 API 执行请求,独立于底层 运输。如果您需要更改任何运输特定的详细信息,请在 现有要使用自定义设置创建新实例:​​HttpGraphQlClient​​​​mutate()​​​​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...

7.1.2. 网络套接字

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

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

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

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

除了面向连接之外,还多路复用。 它为所有请求维护单个共享连接。如果连接丢失, 它在下一个请求或再次调用 IFI 时重新建立。你也可以 使用客户端的方法取消正在进行的请求,关闭 连接,并拒绝新请求。​​WebSocketGraphQlClient​​​​start()​​​​stop()​

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

创建后,您可以开始使用相同的 API 执行请求,独立于底层 运输。如果您需要更改任何运输特定的详细信息,请在 现有要使用自定义设置创建新实例:​​WebSocketGraphQlClient​​​​mutate()​​​​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...
拦截 器

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 请求,但请注意 最多只能是一个类型的拦截器。​​GraphQlClientInterceptor​​​​WebSocketGraphQlClientInterceptor​

7.1.3. RSocket

​RSocketGraphQlClient​​使用RSocketRequester 通过 RSocket请求执行 GraphQL 请求。

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

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

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

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

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

创建后,您可以开始使用相同的 API 执行请求,独立于底层 运输。​​RSocketGraphQlClient​

7.1.4. 生成器

​GraphQlClient​​定义具有通用配置选项的父级 所有扩展的构建器。目前,它允许您配置:​​Builder​

  • ​DocumentSource​​从文件加载请求的文档的策略
  • 拦截已执行的请求

7.2. 请求

一旦你有了GraphQlClient,你就可以开始通过retrieve()或execute()来执行请求,其中前者只是后者的快捷方式。

7.2.1. 检索

下面检索和解码查询的数据:

String document = "{" +
" project(slug:\"spring-framework\") {" +
" name" +
" releases {" +
" version" +
" }"+
" }" +
"}";

Mono<Project> projectMono = graphQlClient.document(document)
.retrieve("project")
.toEntity(Project.class);

要执行的操作。

要从中解码的响应映射中“data”键下的路径。

解码目标类型路径处的数据。

输入文档可以是文字或通过代码生成的 生成的请求对象。您还可以在文件中定义文档,并使用文档源按文件名重新确定它们。​​String​

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

如果给定路径不存在,则解码可能会导致 in,或者 字段值 isand 有错误。提供对 响应和字段:​​FieldAccessException​​​​null​​​​FieldAccessException​

Mono<Project> projectMono = graphQlClient.document(document)
.retrieve("project")
.toEntity(Project.class)
.onErrorResume(FieldAccessException.class, ex -> {
ClientGraphQlResponse response = ex.getResponse();
// ...
ResponseField field = ex.getField();
// ...
});

7.2.2. 执行

检索只是从 响应映射。要获得更多控制,请使用该方法并处理响应:​​execute​

例如:

Mono<Project> projectMono = graphQlClient.document(document)
.execute()
.map(response -> {
if (!response.isValid()) {
// Request failure...
}

ResponseField field = response.field("project");
if (!field.hasValue()) {
if (field.getError() != null) {
// Field failure...
}
else {
// Optional field set to null...
}
}

return field.toEntity(Project.class);
});

响应没有数据,只有错误

具有关联错误的字段​​null​

由其设置的字段​​null​​​​DataFetcher​

解码给定路径中的数据

7.2.3. 文档源

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

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

例如,给定一个名为 in 的文件,其中包含以下内容:​​projectReleases.graphql​​​​src/main/resources/graphql-documents​

src/main/resources/graphql-documents/projectReleases.graphql

query projectReleases($slug: ID!) {
project(slug: $slug) {
name
releases {
version
}
}
}

然后,您可以:

Mono<Project> projectMono = graphQlClient.documentName("projectReleases") 
.variable("slug", "spring-framework")
.retrieve()
.toEntity(Project.class);

从“projectReleases.graphql”加载文档

提供变量值。

IntelliJ 的 “JS GraphQL” 插件支持带有代码完成功能的 GraphQL 查询文件。

您可以使用构建器自定义按名称加载文档。​​GraphQlClient​​​​DocumentSource​

7.3. 订阅请求

​GraphQlClient​​可以通过支持它的传输执行订阅。目前,只有 WebSocket 传输支持 GraphQL 流,因此您需要创建一个WebSocketGraphQlClient。

7.3.1. 检索

要启动订阅流,请使用这类似于检索单个响应,但返回一个流 响应,每个响应解码为一些数据:​​retrieveSubscription​

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

订阅流可能以以下结尾:

  • ​SubscriptionErrorException​​如果服务器结束 包含包含一个或多个 GraphQL 错误的显式“错误”消息的订阅。 该异常提供对从该消息解码的 GraphQL 错误的访问。
  • ​GraphQlTransportException​​例如,如果底层 连接已关闭或丢失,在这种情况下,您可以使用运算符重新建立 连接并重新启动订阅。WebSocketDisconnectedExceptionretry

7.3.2. 执行

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

Flux<String> greetingFlux = client.document("subscription { greetings }")
.executeSubscription()
.map(response -> {
if (!response.isValid()) {
// Request failure...
}

ResponseField field = response.field("project");
if (!field.hasValue()) {
if (field.getError() != null) {
// Field failure...
}
else {
// Optional field set to null...
}
}

return field.toEntity(String.class)
});

7.4. 拦截

您可以创建 ato 以通过客户端拦截所有请求:​​GraphQlClientInterceptor​

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

}

创建拦截器后,通过客户端构建器注册它:

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

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

8. 测试

Spring for GraphQL 为通过 HTTP 测试 GraphQL 请求提供了专门的支持, WebSocket 和 RSocket,以及直接针对服务器进行测试。

要利用这一点,请添加到您的构建中:​​spring-graphql-test​

格拉德尔

马文

dependencies {
// ...
testImplementation 'org.springframework.graphql:spring-graphql-test:1.1.0'
}

8.1. ​​GraphQlTester​

​GraphQlTester​​是一个合约,它声明了一个用于测试 GraphQL 的通用工作流 独立于基础传输的请求。这意味着请求经过测试 使用相同的 API,无论底层传输是什么,以及任何传输 特定在构建时配置。

要创建通过客户端执行请求的 a,您需要 以下扩展:​​GraphQlTester​

  • HttpGraphQlTester
  • WebSocketGraphQlTester
  • RSocketGraphQlTester

要创建在没有客户端的情况下在服务器端执行测试的 a,请执行以下操作:GraphQlTester

  • ExecutionGraphQlServiceTester
  • WebGraphQlServiceTester

每个都定义了与传输相关的选项。所有构建器扩展 来自一个通用的,基本的GraphQlTesterBuilder 与所有扩展相关的选项。​​Builder​

8.1.1. HTTP

​HttpGraphQlTester​​使用网络测试客户端执行 通过 HTTP 的 GraphQL 请求,有或没有实时服务器,具体取决于配置的方式。​​WebTestClient​

要在没有实时服务器的Spring WebFlux中进行测试,请指向您的Spring配置 声明 GraphQL HTTP 端点:

ApplicationContext context = ... ;

WebTestClient client =
WebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("/graphql")
.build();

HttpGraphQlTester tester = HttpGraphQlTester.create(client);

要在没有实时服务器的Spring MVC中进行测试,请使用以下命令执行相同的操作:​​MockMvcWebTestClient​

ApplicationContext context = ... ;

WebTestClient client =
MockMvcWebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("/graphql")
.build();

HttpGraphQlTester tester = HttpGraphQlTester.create(client);

或者,要针对端口上运行的实时服务器进行测试:

WebTestClient client =
WebTestClient.bindToServer()
.baseUrl("http://localhost:8080/graphql")
.build();

HttpGraphQlTester tester = HttpGraphQlTester.create(client);

创建后,您可以开始使用相同的 API 执行请求,独立于底层 运输。如果您需要更改任何运输特定的详细信息,请在 现有要使用自定义设置创建新实例:​​HttpGraphQlTester​​​​mutate()​​​​HttpSocketGraphQlTester​

HttpGraphQlTester tester = HttpGraphQlTester.builder(clientBuilder)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();

// Use tester...

HttpGraphQlTester anotherTester = tester.mutate()
.headers(headers -> headers.setBasicAuth("peter", "..."))
.build();

// Use anotherTester...

8.1.2. 网络套接字

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

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

WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build();

​WebSocketGraphQlTester​​是面向连接的和多路复用的。每个实例都建立 它自己的单个共享连接,用于所有请求。通常,您需要使用单个 仅每个服务器的实例。

创建后,您可以开始使用相同的 API 执行请求,独立于底层 运输。如果您需要更改任何运输特定的详细信息,请在 现有要使用自定义设置创建新实例:​​WebSocketGraphQlTester​​​​mutate()​​​​WebSocketGraphQlTester​

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

WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();

// Use tester...

WebSocketGraphQlTester anotherTester = tester.mutate()
.headers(headers -> headers.setBasicAuth("peter", "..."))
.build();

// Use anotherTester...

​WebSocketGraphQlTester​​提供可用于拥有 WebSocket 的方法 连接关闭,例如在测试运行后。​​stop()​

8.1.3. RSocket

​RSocketGraphQlTester​​使用从 spring-messaging 来执行 GraphQL 通过 RSocket 的请求:​​RSocketRequester​

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

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

​RSocketGraphQlTester​​是面向连接的和多路复用的。每个实例都建立 它自己的单个共享会话,用于所有请求。通常,您需要使用单个 仅每个服务器的实例。您可以使用测试器上的方法关闭 会话显式。​​stop()​

创建后,您可以开始使用相同的 API 执行请求,独立于底层 运输。​​RSocketGraphQlTester​

8.1.4. ​​GraphQlService​

很多时候,在服务器端测试 GraphQL 请求就足够了,而无需使用 客户端通过传输协议发送请求。要直接针对 a 进行测试,请使用扩展:​​ExecutionGraphQlService​​​​ExecutionGraphQlServiceTester​

GraphQlService service = ... ;
ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.create(service);

创建后,您可以开始使用相同的 API 执行请求,独立于底层 运输。​​ExecutionGraphQlServiceTester​

8.1.5. ​​WebGraphQlHandler​

GraphQlService扩展允许您在服务器端进行测试,而无需 一个客户端。但是,在某些情况下,涉及服务器端传输很有用 使用给定的模拟传输输入进行处理。

扩展允许您在移交给之前通过链处理请求 请求执行:​​WebGraphQlTester​​​​WebGraphQlInterceptor​​​​ExecutionGraphQlService​

WebGraphQlHandler handler = ... ;
WebGraphQlTester tester = WebGraphQlTester.create(handler);

此扩展的构建器允许您定义 HTTP 请求详细信息:

WebGraphQlHandler handler = ... ;

WebGraphQlTester tester = WebGraphQlTester.builder(handler)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();

创建后,您可以开始使用相同的 API 执行请求,独立于底层 运输。​​WebGraphQlServiceTester​

8.1.6. 生成器

​GraphQlTester​​定义具有通用配置选项的父级 所有扩展的构建器。它允许您配置以下内容:​​Builder​

  • ​errorFilter​​- 用于抑制预期错误的谓词,以便您可以检查数据 的响应。
  • ​documentSource​​- 从文件加载请求的文档的策略 类路径或其他任何位置。
  • ​responseTimeout​​- 在计时之前等待请求执行完成的时间 外。

8.2. 请求

一旦有了,您就可以开始测试请求了。下面执行一个 查询项目并使用JsonPath提取 响应中的项目发布版本:​​GraphQlTester​

String document = "{" +
" project(slug:\"spring-framework\") {" +
" releases {" +
" version" +
" }"+
" }" +
"}";

graphQlTester.document(document)
.execute()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);

JsonPath 相对于响应的“数据”部分。

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

例如,给定一个名为 in 的文件,其中包含以下内容:​​projectReleases.graphql​​​​src/main/resources/graphql-test​

query projectReleases($slug: ID!) {
project(slug: $slug) {
releases {
version
}
}
}

然后,您可以使用:

graphQlTester.documentName("projectReleases") 
.variable("slug", "spring-framework")
.execute()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);

请参阅名为“项目”的文件中的文档。

设置变量。​​slug​


IntelliJ 的 “JS GraphQL” 插件支持带有代码完成功能的 GraphQL 查询文件。


如果请求没有任何响应数据,例如突变,请使用代替验证响应中没有错误:​​executeAndVerify​​​​execute​

graphQlTester.query(query).executeAndVerify();

有关错误处理的更多详细信息,请参阅错误。

8.3. 订阅

若要测试订阅,请调用而不是获取流 的响应,然后从项目反应器使用来检查流:​​executeSubscription​​​​execute​​​​StepVerifier​

Flux<String> greetingFlux = tester.document("subscription { greetings }")
.executeSubscription()
.toFlux("greetings", String.class); // decode at JSONPath

StepVerifier.create(greetingFlux)
.expectNext("Hi")
.expectNext("Bonjour")
.expectNext("Hola")
.verifyComplete();

订阅仅支持WebSocketGraphQlTester 或服务器端 GraphQlService 和WebGraphQlHandler扩展。

8.4. 错误

使用时,响应中“错误”键下的任何错误都会导致 断言失败。若要禁止显示特定错误,请在以下之前使用错误筛选器:​​verify()​​​​verify()​

graphQlTester.query(query)
.execute()
.errors()
.filter(error -> ...)
.verify()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);

您可以在构建器级别注册错误过滤器,以应用于所有测试:

WebGraphQlTester graphQlTester = WebGraphQlTester.builder(client)
.errorFilter(error -> ...)
.build();

如果要验证错误是否确实存在,并且相反,请抛出 断言错误 如果没有,则使用代替:​​filter​​​​exepect​

graphQlTester.query(query)
.execute()
.errors()
.expect(error -> ...)
.verify()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);

您还可以通过 a 检查所有错误,这样做也会将它们标记为 已筛选,因此您还可以检查响应中的数据:​​Consumer​

graphQlTester.query(query)
.execute()
.errors()
.satisfy(errors -> {
// ...
});

9. 样品

这个 Spring for GraphQL 存储库包含以下示例应用程序 各种场景。

您可以通过克隆此存储库并运行主应用程序类来运行它们 您的 IDE,或在命令行中键入以下内容:

$ ./gradlew :samples:{sample-directory-name}:bootRun