# Spring 用于 GraphQL 文档
# 1. 概述
Spring for GraphQL 为 Spring 构建在GraphQL Java (opens new window)上的应用程序提供支持。这是两个团队的联合合作。我们的共同理念是少些固执己见,更多地关注全面和广泛的支持。
Spring 对于 GraphQL 来说,它是 GraphQL Java 团队的GraphQL Java Spring (opens new window)项目的继承者。它旨在成为所有 Spring、GraphQL 应用程序的基础。
目前,该项目正处于迈向 1.0 版本的里程碑阶段,并正在寻找反馈。请使用我们的问题追踪器 (opens new window)报告问题,讨论设计问题,或请求一个功能。
要开始,请检查start.spring.io (opens new window)上的 Spring GraphQL 启动器和Samples部分。
# 2. 所需经费
Spring 对于 GraphQL,需要以下作为基线:
JDK8
Spring 框架 5.3
GraphQL Java17
Spring 用于 QueryDSL 或通过示例查询的数据 2021.1.0 或更高版本
# 3. 网络传输
Spring for GraphQL 支持 HTTP 和 Over WebSocket 上的 GraphQL 请求。
# 3.1. HTTP
GraphQlHttpHandler
通过 HTTP 请求处理 GraphQL,并将其委托给网络拦截链以执行请求。有两种变体,一种适用于 Spring MVC,另一种适用于 Spring WebFlux。两者都异步处理请求,并具有等效的功能,但在编写 HTTP 响应时分别依赖于阻塞和非阻塞 I/O。
请求必须使用 HTTP POST,而 GraphQL 请求详细信息作为 JSON 包含在请求主体中,如提议的HTTP 上的 GraphQL (opens new window)规范中所定义的那样。一旦成功解码了 JSON 主体,HTTP 响应状态总是 200(OK),来自 GraphQL 请求执行的任何错误都会出现在 GraphQL 响应的“错误”部分。
通过声明RouterFunction
Bean 并使用 Spring MVC 或 WebFlux 中的RouterFunctions
来创建路由,GraphQlHttpHandler
可以作为 HTTP 端点公开。引导启动器执行此操作,请参阅Web 端点 (opens new window)小节以获取详细信息,或检查其包含的GraphQlWebMvcAutoConfiguration
或GraphQlWebFluxAutoConfiguration
以获取实际配置。
Spring for GraphQL 存储库包含一个 Spring MVC应用程序。
# 3.2. WebSocket
GraphQlWebSocketHandler
基于graphql-ws (opens new window)库中定义的protocol (opens new window)处理 WebSocket 请求上的 GraphQL。在 WebSocket 以上使用 GraphQL 的主要原因是订阅允许发送 GraphQL 响应流,但它也可以用于具有单个响应的常规查询。处理程序将每个请求委托给网络拦截链,以进一步执行请求。
GraphQL over WebSocket 协议 有两个这样的协议,一个在订阅-transport-ws (opens new window)库中,另一个在graphql-ws (opens new window)库中。前者不是活动的,而 是由后者继承的。阅读此blog post (opens new window)查看历史。 |
---|
GraphQlWebSocketHandler
有两种变体,一种用于 Spring MVC,另一种用于 Spring WebFlux。两者都异步处理请求,并具有等效的功能。WebFlux 处理程序还使用非阻塞 I/O 和反压来传输消息流,这很好地工作,因为在 GraphQL Java 中,订阅响应是一个反应流Publisher
。
graphql-ws
项目列出了许多用于客户机的recipes (opens new window)。
通过声明SimpleUrlHandlerMapping
Bean 并使用它将处理程序映射到 URL 路径,可以将GraphQlWebSocketHandler
公开为 WebSocket 端点。引导启动器有启用此功能的选项,有关详细信息,请参见Web 端点 (opens new window)部分,或检查其包含的GraphQlWebMvcAutoConfiguration
或GraphQlWebFluxAutoConfiguration
以获取实际配置。
Spring for GraphQL 存储库包含一个 WebFluxWebSocket sample (opens new window)应用程序。
# 3.3. 网络拦截
HTTP和WebSocket传输处理程序委托给公共 Web 拦截链以执行请求。该链由WebInterceptor
组件序列组成,然后是调用 GraphQL Java 引擎的GraphQlService
组件序列。
WebInterceptor
是在 Spring MVC 和 WebFlux 应用程序中使用的通用契约。使用它来拦截请求、检查 HTTP 请求头或注册graphql.ExecutionInput
的转换:
class MyInterceptor implements WebInterceptor {
@Override
public Mono<WebOutput> intercept(WebInput webInput, WebInterceptorChain chain) {
webInput.configureExecutionInput((executionInput, builder) -> {
Map<String, Object> map = ... ;
return builder.extensions(map).build();
});
return chain.next(webInput);
}
}
也可以使用WebInterceptor
来拦截响应,添加 HTTP 响应头,或转换graphql.ExecutionResult
:
class MyInterceptor implements WebInterceptor {
@Override
public Mono<WebOutput> intercept(WebInput webInput, WebInterceptorChain chain) {
return chain.next(webInput)
.map(webOutput -> {
Object data = webOutput.getData();
Object updatedData = ... ;
return webOutput.transform(builder -> builder.data(updatedData));
});
}
}
WebGraphQlHandler
提供了一个构建器来初始化 Web 拦截链。在构建链后,可以使用生成的WebGraphQlHandler
初始化 HTTP 或 WebSocket 传输处理程序。引导启动器配置所有这些,请参阅Web 端点 (opens new window)小节以获取详细信息,或检查其包含的GraphQlWebMvcAutoConfiguration
或GraphQlWebFluxAutoConfiguration
以获取实际配置。
# 4. 请求执行
GraphQlService
是调用 GraphQL Java 执行请求的主要 Spring 抽象。底层传输,例如网络传输,委托给GraphQlService
来处理请求。
主要的实现ExecutionGraphQlService
是围绕graphql.GraphQL
调用的一个很薄的外观。它配置了GraphQlSource
以访问graphql.GraphQL
实例。
# 4.1. GraphQLSource
GraphQlSource
是用于访问用于执行请求的graphql.GraphQL
实例的核心 Spring 抽象。它提供了一个 Builder API 来初始化 GraphQL Java 并构建GraphQlSource
。
默认的GraphQlSource
Builder 可通过GraphQlSource.builder()
访问,支持[activeDataFetcher
](#execution-active-datafetcher)、上下文传播和异常解决。
Spring bootstarter (opens new window)通过默认的GraphQlSource.Builder
初始化GraphQlSource
实例,并启用以下功能:
从可配置位置加载模式文件。
公开适用于
GraphQlSource.Builder
的properties (opens new window)。检测[
RuntimeWiringConfigurer
]bean。检测仪器仪表 (opens new window)bean 的GraphQL 度量 (opens new window)。
检测
DataFetcherExceptionResolver
bean 的异常解决。检测
GraphQlSourceBuilderCustomizer
bean 的任何其他自定义。
# 4.1.1. 模式资源
GraphQlSource.Builder
可以配置一个或多个Resource
实例来进行解析和合并。这意味着模式文件可以从几乎任何位置加载。
默认情况下, Spring 引导启动器查找架构文件 (opens new window)来自一个众所周知的 Classpath 位置,但是你可以通过FileSystemResource
将其更改为文件系统上的一个位置,通过ByteArrayResource
将字节内容更改为通过ByteArrayResource
,或者实现一个自定义的Resource
从远程位置或存储空间加载模式文件。
# 4.1.2. 模式创建
默认情况下,GraphQlSource.Builder
使用 GraphQLJava<gtr="145"/>来创建<gtr="146"/>。这适用于大多数应用程序,但如果有必要,你可以通过构建器连接到模式创建:
// Typically, accessed through Spring Boot's GraphQlSourceBuilderCustomizer
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.schemaFactory((typeDefinitionRegistry, runtimeWiring) -> {
// create GraphQLSchema
})
这样做的主要原因是通过联合库创建模式。
# 4.1.3. RuntimeWiringConfigurer
你可以使用RuntimeWiringConfigurer
注册:
自定义标量类型。
指令处理代码。
TypeResolver
,如果需要覆盖类型的[defaultTypeResolver
](#execution-grapqlsource-default-type-resolver)。对于字段
DataFetcher
,尽管大多数应用程序只会简单地配置AnnotatedControllerConfigurer
,其中检测带注释的DataFetcher
处理程序方法。 Spring 引导启动器默认添加AnnotatedControllerConfigurer
。
Spring 引导启动器检测类型RuntimeWiringConfigurer
的 bean,并将它们注册在GraphQlSource.Builder
中。这意味着,在大多数情况下,在配置中都会有如下内容:
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(BookRepository repository) {
GraphQLScalarType scalarType = ... ;
SchemaDirectiveWiring directiveWiring = ... ;
DataFetcher dataFetcher = QuerydslDataFetcher.builder(repository).single();
return wiringBuilder -> wiringBuilder
.scalar(scalarType)
.directiveWiring(directiveWiring)
.type("Query", builder -> builder.dataFetcher("book", dataFetcher));
}
}
如果需要添加WiringFactory
,例如,为了使注册考虑到模式定义,实现替代的configure
方法,该方法同时接受RuntimeWiring.Builder
和输出List<WiringFactory>
。这允许你添加任意数量的工厂,然后按顺序调用这些工厂。
# 4.1.4. 默认TypeResolver
GraphQlSource.Builder
将ClassNameTypeResolver
注册为默认的TypeResolver
,用于尚未通过[RuntimeWiringConfigurer
]进行注册的 GraphQL 接口和联合。在 GraphQL Java 中,TypeResolver
的目的是确定从DataFetcher
返回的值的 GraphQL 对象类型,用于 GraphQL 接口或 Union 字段。
ClassNameTypeResolver
尝试将该值的简单类名与 GraphQL 对象类型匹配,如果不成功,它还会导航其超级类型,包括基类和接口,以寻找匹配。ClassNameTypeResolver
提供了一个选项,可以将名称提取函数与Class
一起配置为 GraphQL 对象类型名称映射,这应该有助于覆盖更多的角情况。
# 4.1.5. 操作缓存
GraphQL Java 在执行操作之前必须解析和验证。这可能会对业绩产生重大影响。为了避免重新解析和验证的需要,应用程序可以配置一个PreparsedDocumentProvider
来缓存和重用文档实例。GraphQL Java DOCS (opens new window)通过PreparsedDocumentProvider
提供有关查询缓存的更多详细信息。
在 Spring GraphQL 中,你可以通过GraphQlSource.Builder#configureGraphQl
注册一个PreparsedDocumentProvider
:。
// Typically, accessed through Spring Boot's GraphQlSourceBuilderCustomizer
GraphQlSource.Builder builder = ...
// Create provider
PreparsedDocumentProvider provider = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.configureGraphQl(graphQLBuilder -> graphQLBuilder.preparsedDocumentProvider(provider))
# 4.1.6. 指令
GraphQL 语言支持“描述 GraphQL 文档中的替代运行时执行和类型验证行为”的指令。指令类似于 Java 中的注释,但在 GraphQL 文档中对类型、字段、片段和操作进行了声明。
GraphQL Java 提供SchemaDirectiveWiring
契约来帮助应用程序检测和处理指令。有关更多详细信息,请参见 GraphQL Java 文档中的模式指令 (opens new window)。
在 Spring GraphQL 中,可以通过[RuntimeWiringConfigurer
](#execution-graphqlsource-runtimewilling-configurer)注册SchemaDirectiveWiring
。 Spring 引导启动器会检测到这样的 bean,因此你可能会有如下内容:
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return builder -> builder.directiveWiring(new MySchemaDirectiveWiring());
}
}
对于指令支持的示例,请查看GraphQL Java 的扩展验证 (opens new window)库。 |
---|
# 4.2. Reactive DataFetcher
默认的GraphQlSource
Builder 使对DataFetcher
的支持返回Mono
或Flux
,这将这些支持调整为CompletableFuture
,其中Flux
值被聚合并转换为一个列表,除非该请求是 GraphQL 订阅请求,在这种情况下,返回值仍然是用于流式 GraphQL 响应的反应流Publisher
。
反应性DataFetcher
可以依赖于对从传输层传播的反应器上下文的访问,例如来自 WebFlux 请求的处理,请参见WebFlux 上下文。
# 4.3. 执行上下文
Spring 对于 GraphQL 提供了支持,以通过 GraphQL 引擎从网络传输透明地传播上下文,并支持DataFetcher
和它调用的其他组件。这既包括来自 Spring MVC 请求处理线程的ThreadLocal
上下文,也包括来自 WebFlux 处理管道的反应器Context
上下文。
# 4.3.1. WebMVC
GraphQL Java 调用的DataFetcher
和其他组件可能并不总是在与 Spring MVC 处理程序相同的线程上执行,例如,如果异步[WebInterceptor
]或DataFetcher
切换到不同的线程。
Spring 对于 GraphQL 支持将ThreadLocal
值从 Servlet 容器线程传播到DataFetcher
线程和由 GraphQL 引擎调用的其他组件上执行。要做到这一点,应用程序需要创建ThreadLocalAccessor
以提取感兴趣的ThreadLocal
值:
public class RequestAttributesAccessor implements ThreadLocalAccessor {
private static final String KEY = RequestAttributesAccessor.class.getName();
@Override
public void extractValues(Map<String, Object> container) {
container.put(KEY, RequestContextHolder.getRequestAttributes());
}
@Override
public void restoreValues(Map<String, Object> values) {
if (values.containsKey(KEY)) {
RequestContextHolder.setRequestAttributes((RequestAttributes) values.get(KEY));
}
}
@Override
public void resetValues(Map<String, Object> values) {
RequestContextHolder.resetRequestAttributes();
}
}
可以在Webgraphandler构建器中注册ThreadLocalAccessor
。引导启动器检测这种类型的 bean 并自动为 Spring MVC 应用程序注册它们,请参见Web 端点 (opens new window)小节。
# 4.3.2. WebFlux
[acceptiveDataFetcher
](#execution-acceptive-datafetcher)可以依赖于对源自 WebFlux 请求处理链的反应器上下文的访问。这包括由网络拦截器组件添加的反应堆上下文。
# 4.4. 异常解决
GraphQL Java 应用程序可以注册DataFetcherExceptionHandler
,以决定如何在 GraphQL 响应的“错误”部分中表示来自数据层的异常。
Spring 对于 GraphQL,有一个内置的DataFetcherExceptionHandler
,它被配置为由[GraphQLSource
](#execution-grapqlsource)生成器使用。它使应用程序能够注册一个或多个 Spring DataFetcherExceptionResolver
按顺序调用的组件,直到将Exception
解析为graphql.GraphQLError
对象的列表。
DataFetcherExceptionResolver
是一种异步契约。对于大多数实现方式,扩展DataFetcherExceptionResolverAdapter
并覆盖其同步解决异常的resolveToSingleError
或resolveToMultipleErrors
方法之一就足够了。
aGraphQLError
可以被分配一个graphql.ErrorClassification
。 Spring 对于 GraphQL 定义了一个ErrorType
枚举,具有常见的、错误的分类类别:
BAD_REQUEST
UNAUTHORIZED
FORBIDDEN
NOT_FOUND
INTERNAL_ERROR
应用程序可以使用它来对错误进行分类。如果错误仍未解决,则默认情况下将其标记为INTERNAL_ERROR
。
# 4.5. 批量装载
给定一个Book
和它的Author
,我们可以为一本书创建一个DataFetcher
,为它的作者创建另一个。这允许在有或没有作者的情况下选择书籍,但这意味着书籍和作者 AREN 不能一起加载,这在查询多本书时效率特别低,因为每本书的作者都是单独加载的。这就是所谓的 N+1 选择问题。
# 4.5.1. DataLoader
GraphQL Java 提供了一种DataLoader
机制,用于批量加载相关实体。你可以在GraphQL Java DOCS (opens new window)中找到完整的详细信息。以下是其工作原理的摘要:
在
DataLoaderRegistry
中注册DataLoader
的可以加载实体的项,给定唯一的键。DataFetcher
的可以访问DataLoader
的,并使用它们通过 ID 加载实体。a
DataLoader
通过返回 future 来延迟加载,因此可以在批处理中完成。DataLoader
的每请求保持一个加载实体的缓存,这可以进一步提高效率。
# 4.5.2. BatchLoaderRegistry
GraphQL Java 中完整的批处理加载机制需要实现几个BatchLoader
接口中的一个,然后用DataLoader
中的名称将这些接口包装并注册为DataLoader
s。
Spring GraphQL 中的 API 略有不同。对于注册,只有一个 centralBatchLoaderRegistry
公开工厂方法和构建器,以创建和注册任意数量的批处理加载函数:
@Configuration
public class MyConfig {
public MyConfig(BatchLoaderRegistry registry) {
registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
// return Mono<Map<Long, Author>
});
// more registrations ...
}
}
Spring 引导启动器声明一个BatchLoaderRegistry
Bean,你可以将其注入到你的配置中,如上面所示,或注入到任何组件中,例如控制器中的顺序寄存器批处理加载功能。然后,将BatchLoaderRegistry
注入ExecutionGraphQlService
,从而确保每个请求的注册量DataLoader
。
默认情况下,DataLoader
名称是基于目标实体的类名。这允许@SchemaMapping
方法使用泛型类型声明Dataloader 参数,而不需要指定名称。但是,如果有必要,可以通过BatchLoaderRegistry
Builder 自定义名称,以及其他DataLoader
选项。
对于许多情况,在加载相关实体时,可以使用@batchmapping控制器方法,这对于需要使用BatchLoaderRegistry
和DataLoader
直接替换的快捷方式也提供了重要的好处。SBatchLoaderRegistry
也提供了其他好处。它支持从批装载函数和从@BatchMapping
方法访问相同的GraphQLContext
,并确保上下文传播到它们。这就是为什么应用程序需要使用它。可以直接执行你自己的DataLoader
注册,但这种注册将放弃上述好处。
# 4.5.3. 测试批装载
首先让BatchLoaderRegistry
在DataLoaderRegistry
上执行注册:
BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry();
// perform registrations...
DataLoaderRegistry dataLoaderRegistry = DataLoaderRegistry.newRegistry().build();
batchLoaderRegistry.registerDataLoaders(dataLoaderRegistry, graphQLContext);
现在你可以访问和测试个别DataLoader
的如下所示:
DataLoader<Long, Book> loader = dataLoaderRegistry.getDataLoader(Book.class.getName());
loader.load(1L);
loader.loadMany(Arrays.asList(2L, 3L));
List<Book> books = loader.dispatchAndJoin(); // actual loading
assertThat(books).hasSize(3);
assertThat(books.get(0).getName()).isEqualTo("...");
// ...
# 5. 数据整合
Spring 对于 GraphQL,你可以利用现有的 Spring 技术,遵循常见的编程模型来通过 GraphQL 公开底层数据源。
本节讨论了用于 Spring 数据的集成层,该集成层提供了一种简单的方法,将 QueryDSL 或查询 by example 存储库调整为DataFetcher
,包括用于标记为@GraphQlRepository
的存储库的自动检测和 GraphQL 查询注册的选项。
# 5.1. QueryDSL
Spring 对于 GraphQL 支持使用Querydsl (opens new window)来通过 Spring 数据获取数据QueryDSL 扩展 (opens new window)。QueryDSL 提供了一种灵活的 TypeSafe 方法,通过使用注释处理器生成元模型来表示查询谓词。
例如,将存储库声明为QuerydslPredicateExecutor
:
public interface AccountRepository extends Repository<Account, Long>,
QuerydslPredicateExecutor<Account> {
}
然后使用它创建DataFetcher
:
// For single result queries
DataFetcher<Account> dataFetcher =
QuerydslDataFetcher.builder(repository).single();
// For multi-result queries
DataFetcher<Iterable<Account>> dataFetcher =
QuerydslDataFetcher.builder(repository).many();
你现在可以通过[RuntimeWiringConfigurer
](#execution-graphqlsource-runtimewilling-configurer)注册上面的DataFetcher
。
DataFetcher
从 GraphQL 请求参数构建一个 QueryDSLPredicate
,并使用它来获取数据。 Spring 对于 JPA、MongoDB 和 LDAP,数据支持QuerydslPredicateExecutor
。
如果存储库是ReactiveQuerydslPredicateExecutor
,则构建器返回DataFetcher<Mono<Account>>
或DataFetcher<Flux<Account>>
。 Spring 数据支持 MongoDB 的这种变体。
# 5.1.1. 构建设置
要在构建中配置 QueryDSL,请遵循正式参考文件 (opens new window):
例如:
Gradle
dependencies {
//...
annotationProcessor "com.querydsl:querydsl-apt:$querydslVersion:jpa",
'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final',
'javax.annotation:javax.annotation-api:1.3.2'
}
compileJava {
options.annotationProcessorPath = configurations.annotationProcessor
}
Maven
<dependencies>
<!-- ... -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<classifier>jpa</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.2.Final</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
<plugins>
<!-- Annotation processor configuration -->
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>${apt-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
WebMVC-HTTP (opens new window)样本使用 queryDSL 表示artifactRepositories
。
# 5.1.2. 定制
QuerydslDataFetcher
支持自定义如何将 GraphQL 参数绑定到属性上以创建 QueryDSLPredicate
。默认情况下,对于每个可用的属性,参数被绑定为“is equaled to”。要对此进行定制,可以使用QuerydslDataFetcher
Builder 方法来提供QuerydslBinderCustomizer
。
存储库本身可能是QuerydslBinderCustomizer
的实例。这是在自动注册期间自动检测和透明地应用的。然而,当手动构建QuerydslDataFetcher
时,你将需要使用 Builder 方法来应用它。
QuerydslDataFetcher
支持接口和 DTO 投影来转换查询结果,然后返回这些结果进行进一步的 GraphQL 处理。
要了解投影是什么,请参阅Spring Data docs (opens new window)。 要了解如何在 GraphQL 中使用投影,请参见选择集与投影。 |
---|
要在 QueryDSL 存储库中使用 Spring 数据投影,请创建一个投影接口或一个目标 DTO 类,并通过projectAs
方法对其进行配置,以获得产生目标类型的DataFetcher
:
class Account {
String name, identifier, description;
Person owner;
}
interface AccountProjection {
String getName();
String getIdentifier();
}
// For single result queries
DataFetcher<AccountProjection> dataFetcher =
QuerydslDataFetcher.builder(repository).projectAs(AccountProjection.class).single();
// For multi-result queries
DataFetcher<Iterable<AccountProjection>> dataFetcher =
QuerydslDataFetcher.builder(repository).projectAs(AccountProjection.class).many();
# 5.1.3. 自动注册
如果存储库使用@GraphQlRepository
进行注释,那么对于尚未注册DataFetcher
且其返回类型与存储库域类型匹配的查询,将自动对其进行注册。这包括单值查询和多值查询。
默认情况下,查询返回的 GraphQL 类型的名称必须与存储库域类型的简单名称匹配。如果需要,可以使用@GraphQlRepository
的typeName
属性来指定目标 GraphQL 类型名。
自动注册检测给定存储库是否实现QuerydslBinderCustomizer
,并通过QuerydslDataFetcher
Builder 方法透明地应用该方法。
自动注册是通过可从QuerydslDataFetcher
获得的内置RuntimeWiringConfigurer
执行的。引导启动器 (opens new window)自动检测@GraphQlRepository
bean,并使用它们初始化RuntimeWiringConfigurer
with。
自动注册不支持定制。如果需要,你需要使用QueryByExampleDataFetcher
通过[RuntimeWiringConfigurer
](#execution-graphqlsource-runtimewilling-configurer)手动构建和注册DataFetcher
。
# 5.2. 示例查询
Spring 数据支持使用示例查询 (opens new window)来获取数据。Query by Example 是一种简单的查询技术,不需要你通过特定于存储的查询语言编写查询。
从声明QueryByExampleExecutor
的存储库开始:
public interface AccountRepository extends Repository<Account, Long>,
QueryByExampleExecutor<Account> {
}
使用QueryByExampleDataFetcher
将存储库转换为DataFecher
:
// For single result queries
DataFetcher<Account> dataFetcher =
QueryByExampleDataFetcher.builder(repository).single();
// For multi-result queries
DataFetcher<Iterable<Account>> dataFetcher =
QueryByExampleDataFetcher.builder(repository).many();
你现在可以通过[RuntimeWiringConfigurer
]注册上面的DataFetcher
(#execution-graphqlsource-runtimewilling-configurer)。
DataFetcher
使用 GraphQL 参数映射来创建存储库的域类型,并将其作为示例对象来获取数据。 Spring 对于 JPA、MongoDB、NEO4J 和 Redis,数据支持QueryByExampleDataFetcher
。
如果存储库是ReactiveQueryByExampleExecutor
,则构建器返回DataFetcher<Mono<Account>>
或DataFetcher<Flux<Account>>
。 Spring 数据支持 MongoDB、NEO4J、Redis 和 R2DBC 的这种变体。
# 5.2.1. 构建设置
Spring 对于支持它的数据存储,示例查询已经包括在 Spring 数据模块中,因此不需要额外的设置来启用它。
# 5.2.2. 定制
QueryByExampleDataFetcher
支持接口和 DTO 投影来转换查询结果,然后返回这些结果进行进一步的 GraphQL 处理。
要了解投影是什么,请参阅Spring Data documentation (opens new window)。 要了解投影在 GraphQL 中的作用,请参见选择集与投影。 |
---|
Spring 要通过示例存储库查询使用数据投影,可以创建投影接口或目标 DTO 类,并通过projectAs
方法对其进行配置,以获得产生目标类型的DataFetcher
:
class Account {
String name, identifier, description;
Person owner;
}
interface AccountProjection {
String getName();
String getIdentifier();
}
// For single result queries
DataFetcher<AccountProjection> dataFetcher =
QueryByExampleDataFetcher.builder(repository).projectAs(AccountProjection.class).single();
// For multi-result queries
DataFetcher<Iterable<AccountProjection>> dataFetcher =
QueryByExampleDataFetcher.builder(repository).projectAs(AccountProjection.class).many();
# 5.2.3. 自动注册
如果存储库使用@GraphQlRepository
进行注释,那么对于尚未注册DataFetcher
且其返回类型与存储库域类型匹配的查询,将自动对其进行注册。这包括单值查询和多值查询。
默认情况下,查询返回的 GraphQL 类型的名称必须与存储库域类型的简单名称匹配。如果需要,可以使用@GraphQlRepository
的typeName
属性来指定目标 GraphQL 类型名。
自动注册是通过可从QueryByExampleDataFetcher
获得的内置RuntimeWiringConfigurer
执行的。引导启动器 (opens new window)自动检测@GraphQlRepository
bean,并使用它们初始化RuntimeWiringConfigurer
with。
自动注册不支持定制。如果需要,你需要使用QueryByExampleDataFetcher
通过[RuntimeWiringConfigurer
]手动构建和注册DataFetcher
(#execution-grapqlsource-runtimewilling-configurer)。
# 5.3. 选择集与投影
出现的一个常见问题是,GraphQL 选择集与Spring Data projections (opens new window)相比如何,每个选择集起什么作用?
简短的回答是, Spring for GraphQL 不是将 GraphQL 查询直接转换为 SQL 或 JSON 查询的数据网关。相反,它允许你利用现有的 Spring 技术,并且不假定 GraphQL 模式和底层数据模型之间存在一对一的映射。这就是为什么客户机驱动的选择和数据模型的服务器端转换可以发挥互补作用的原因。
为了更好地理解,考虑 Spring 数据促进了域驱动(DDD)设计,作为管理数据层中复杂性的推荐方法。在 DDD 中,坚持总量约束是很重要的。根据定义,聚合只有在全部加载的情况下才是有效的,因为部分加载的聚合可能会对聚合功能施加限制。
在 Spring 数据中,你可以选择是希望将聚合按原样公开,还是在将其作为 GraphQL 结果返回之前将转换应用于数据模型。有时,做前者就足够了,默认情况下,Querydsl和示例查询集成将 GraphQL 选择集转换为属性路径提示,底层 Spring 数据模块使用这些属性路径提示来限制选择。
在其他情况下,为了适应 GraphQL 模式,减少甚至转换底层数据模型是有用的。 Spring 数据通过接口和 DTO 投影来支持这一点。
接口投影定义了一组固定的属性,根据数据存储查询结果,在其中属性可以null
,也可以不是null
。有两种类型的接口预测,它们都决定从底层数据源加载哪些属性:
如果不能部分实现聚合对象,但仍然希望公开属性的子集,则闭合界面投影 (opens new window)是有帮助的。
开放接口投影 (opens new window)利用 Spring 的
@Value
注释和SpEL (opens new window)表达式来应用轻量级数据转换,例如连接、计算或将静态函数应用于属性。
DTO 投影提供了更高级别的定制,因为你可以将转换代码放置在构造函数或 getter 方法中。
DTO 投影是通过查询实现的,查询中的各个属性是由投影本身确定的。DTO 投影通常用于 Full-Args 构造函数(例如 Java 记录),因此只有当所有必需的字段(或列)都是数据库查询结果的一部分时,才能构造它们。
# 6. 带注释的控制器
Spring 对于 GraphQL 提供了一种基于注释的编程模型,其中@Controller
组件使用注释来声明具有灵活方法签名的处理程序方法,以获取特定 GraphQL 字段的数据。例如:
@Controller
public class GreetingController {
@QueryMapping (1)
public String hello() { (2)
return "Hello, world!";
}
}
1 | 将此方法绑定到查询,即查询类型下的字段。 |
---|---|
2 | 如果未在注释上声明,则从方法名确定查询。 |
Spring 对于 GraphQL 使用RuntimeWiring.Builder
将上述处理程序方法注册为用于名为“Hello”的查询的graphql.schema.DataFetcher
。
# 6.1. 声明
你可以将@Controller
bean 定义为标准 Spring Bean 定义。该@Controller
原型允许自动检测,与 Spring 对齐,用于在 Classpath 上检测@Controller
和@Component
类并为它们自动注册 Bean 定义。它还充当带注释的类的原型,指示其作为 GraphQL 应用程序中的数据获取组件的角色。
AnnotatedControllerConfigurer
通过RuntimeWiring.Builder
检测@Controller
bean 并将其注释的处理程序方法注册为DataFetcher
s。它是RuntimeWiringConfigurer
的一个实现,它可以被添加到GraphQlSource.Builder
中。 Spring 引导启动器会自动将AnnotatedControllerConfigurer
声明为 Bean,并将所有RuntimeWiringConfigurer
bean 添加到GraphQlSource.Builder
中,从而支持带注释的DataFetcher
s,请参见引导启动器文档中的GraphQL RuntimeWiring (opens new window)部分。
# 6.2. @SchemaMapping
@SchemaMapping
注释将处理程序方法映射到 GraphQL 模式中的字段,并声明该字段为该字段的DataFetcher
。注释可以指定父类型名和字段名:
@Controller
public class BookController {
@SchemaMapping(typeName="Book", field="author")
public Author getAuthor(Book book) {
// ...
}
}
@SchemaMapping
注释也可以省略这些属性,在这种情况下,字段名称默认为方法名称,而类型名称默认为注入到方法中的源/父对象的简单类名。例如,以下默认输入“book”和字段“author”:
@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
处理程序方法具有灵活的签名,可以从一系列方法参数和返回值中进行选择。
# 6.2.1. 方法签名
模式映射处理程序方法可以具有以下任何一个方法参数:
Method Argument | 说明 |
---|---|
@Argument | 要访问将命名字段参数转换为更高级别的类型化对象。 请参见[ @Argument ](#controllers-schema-mapping-convention)。 |
@Arguments | 有关对转换为更高级别的类型化对象的所有字段参数的访问。 参见[ @Arguments ](#controllers-schema-mapping-arguments)。 |
@ProjectedPayload Interface | 通过项目接口访问字段参数。 参见[ @ProjectPayload 接口](#controllers-schema-mapping-projectedpayload-pargument)。 |
Source | 关于字段的源(即父/容器)实例的访问。 参见Source。 |
DataLoader | 要访问DataLoader 中的DataLoader 。请参见[ DataLoader ](#controllers-schema-mapping-data-loader)。 |
@ContextValue | 对于从 localContext 访问一个值,如果它是GraphQLContext 、的实例,或者来自 GraphQLContext 的DataFetchingEnvironment 的实例。 |
GraphQLContext | 用于从DataFetchingEnvironment 访问上下文。 |
java.security.Principal | 从 Spring 安全上下文中获得的,如果可用的话。 |
@AuthenticationPrincipal | 用于从 Spring 安全上下文访问Authentication#getPrincipal() 。 |
DataFetchingFieldSelectionSet | 用于通过DataFetchingEnvironment 访问查询的选择集。 |
Locale , Optional<Locale> | 从DataFetchingEnvironment 访问Locale 。 |
DataFetchingEnvironment | 直接访问底层DataFetchingEnvironment 。 |
模式映射处理程序方法可以返回任意值,包括反应器Mono
和Flux
中描述的[ractiveDataFetcher
](#execution-ractive-datafetcher)。
# 6.2.2. @Argument
在 GraphQL Java 中,DataFetchingEnvironment
提供对特定字段参数值的映射的访问。这些值可以是简单的标量值(例如 String,long)、用于更复杂输入的Map
的值,或者是List
的值。
使用@Argument
注释将命名字段参数注入到处理程序方法中。方法参数可以是任何类型的更高级别的类型化对象。它是根据已命名字段参数的值创建和初始化的,或者将它们匹配到单个数据构造函数参数,或者使用默认构造函数,然后通过org.springframework.validation.DataBinder
将键匹配到对象属性上:
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}
@MutationMapping
public Book addBook(@Argument BookInput bookInput) {
// ...
}
}
默认情况下,如果方法参数名是可用的(需要-parameters
带有 Java8+ 或来自编译器的调试信息的编译器标志),它将用于查找参数。如果需要,可以通过注释自定义名称,例如@Argument("bookInput")
。
@Argument 注释没有“required”标志,也没有指定默认值的选项。这两个都可以在 GraphQL 模式级别指定, 由 GraphQL 引擎强制执行。 |
---|
你可以在Map<String, Object>
参数上使用@Argument
,以获得所有参数的值。不能设置@Argument
上的 name 属性。
# 6.2.3. @Arguments
如果你想将完整的参数映射到单个目标对象上,请使用@Arguments
注释,与@Argument
相反,后者绑定一个特定的命名参数。
例如,@Argument BookInput bookInput
使用参数“bookinput”的值初始化BookInput
,而@Arguments
使用完整的参数映射,在这种情况下,顶层参数绑定到BookInput
属性。
# `验证
如果在应用程序上下文中存在一个Bean Validation (opens new window)Validator
(或者通常,一个LocalValidatorFactoryBean
) Bean,则AnnotatedControllerConfigurer
将自动检测它并配置用于验证的支持。然后在方法调用之前验证用@Valid
和@Validated
注释的控制器参数。
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) {
// ...
}
}
如果在验证过程中发生错误,将抛出一个ConstraintViolationException
,并可以在以后[使用自定义DataFetcherExceptionResolver
解决](#execution-exceptions)。
与 Spring MVC 不同,处理程序方法签名不支持注入BindingResult 以对验证错误做出反应:这些将作为例外情况在全局范围内处理。 |
---|
# 6.2.5. @ProjectPayload
接口
作为使用带有[@Argument
](#controllers-schema-mapping-argument)的完整对象的一种替代方法,你还可以使用投影接口通过定义良好的最小接口访问 GraphQL 请求参数。当 Spring 数据在类路径上时,参数投影由Spring Data’s Interface projections (opens new window)提供。
要利用这一点,请创建一个带有@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();
}
# 6.2.6. 来源
在 GraphQL Java 中,DataFetchingEnvironment
提供对字段的源(即父/容器)实例的访问。要访问它,只需声明一个预期目标类型的方法参数。
@Controller
public class BookController {
@SchemaMapping
public Author author(Book book) {
// ...
}
}
源方法参数还有助于确定映射的类型名。如果 Java 类的简单名称与 GraphQL 类型匹配,则不需要在@SchemaMapping
注释中显式指定类型名称。
[@BatchMapping ](#controllers-batch-mapping)处理程序方法可以为一个查询批装载所有作者,给定源/父书对象的列表。 |
---|
# 6.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());
}
}
默认情况下,BatchLoaderRegistry
使用值类型的完整类名(例如,Author
的类名)作为注册的键,因此只需声明带有泛型类型的DataLoader
方法参数,就可以提供足够的信息来在DataLoaderRegistry
中定位它。作为后备,DataLoader
方法参数解析器也将尝试方法参数名称作为键,但通常不需要这样做。
请注意,对于许多加载相关实体的情况,其中@SchemaMapping
只是将其委托给DataLoader
,你可以使用@batchmapping方法来减少样板文件,如下一节所述。
# 6.3. @BatchMapping
批量装载通过使用org.dataloader.DataLoader
来延迟单个实体实例的加载,从而解决了 N+1SELECT 问题,从而可以将它们一起加载。例如:
@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) {
// ...
}
}
上面变成了BatchLoaderRegistry
中的批处理加载函数,其中键是Book
实例和加载的值它们的作者。此外,DataFetcher
也透明地绑定到类型author
的author
字段,对于作者来说,它只是将其委托给DataLoader
,给定其源/父Book
实例。
要作为唯一键使用,Book 必须实现hashcode 和equals 。 |
---|
默认情况下,字段名称默认为方法名称,而类型名称默认为输入List
元素类型的简单类名。两者都可以通过注释属性进行定制。类型名也可以从类级别@SchemaMapping
继承。
# 6.3.1. 方法签名
批处理映射方法支持以下参数:
Method Argument | 说明 |
---|---|
List<K> | 源/父对象。 |
java.security.Principal | 从 Spring 安全上下文获得(如果可用的话)。 |
@ContextValue | 要访问来自GraphQLContext 的BatchLoaderEnvironment 的值,请使用,该上下文与来自 DataFetchingEnvironment 的上下文相同。 |
GraphQLContext | 要访问来自BatchLoaderEnvironment 的上下文,请访问,该上下文与来自 DataFetchingEnvironment 的上下文相同。 |
BatchLoaderEnvironment | 在 GraphQL Java 中可用的环境为org.dataloader.BatchLoaderWithContext 。 |
批处理映射方法可以返回:
Return Type | 说明 |
---|---|
Mono<Map<K,V>> | 以父对象为键,以批处理加载对象为值的映射。 |
Flux<V> | 批装载对象的序列,其顺序必须与传递到方法中的源/父 对象的顺序相同。 |
Map<K,V> , List<V> | 命令式变体,例如,不需要进行远程调用。 |
# 7. 安全
可以使用 HTTP URL 安全性来保护WebGraphQL 端点的路径,以确保只有经过身份验证的用户才能访问它。然而,这并不能区分在单个 URL 上的共享端点上的不同 GraphQL 请求。
要应用更细粒度的安全性,可以在获取 GraphQL 响应的特定部分所涉及的服务方法中添加 Spring 安全性注释,例如@PreAuthorize
或@Secured
。由于上下文传播的目的是使安全性和其他上下文在数据获取级别可用,所以这种方法应该有效。
Spring for GraphQL 存储库包含Spring MVC (opens new window)和WebFlux (opens new window)的示例。
# 8. 测试
用 Spring 的WebTestClient
测试 GraphQL 请求是可能的,只需要发送和接收 JSON,但是许多 GraphQL 特定的细节使得这种方法比必要的更麻烦。
要获得完整的测试支持,你需要在构建中添加spring-graphql-test
依赖项:
Gradle
dependencies {
// ...
testImplementation 'org.springframework.graphql:spring-graphql-test:1.0.0-SNAPSHOT'
}
Maven
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
# 8.1. GraphQlTester
GraphQlTester
定义了一个工作流,用于测试 GraphQL 请求,该工作流具有以下优点:
在响应中的“errors”键下验证没有意外错误。
在响应中的“数据”键下进行解码。
使用 JSONPath 来解码响应的不同部分。
测试订阅。
要创建GraphQlTester
,只需要一个GraphQlService
,并且不需要传输:
GraphQlSource graphQlSource = GraphQlSource.builder()
.schemaResources(...)
.runtimeWiringConfigurer(...)
.build();
GraphQlService graphQlService = new ExecutionGraphQlService(graphQlSource);
GraphQlTester graphQlTester = GraphQlTester.builder(graphQlService).build();
# 8.2. WebGraphQlTester
WebGraphQlTester
扩展GraphQlTester
以添加特定于网络传输的工作流和配置,并且它始终验证 GraphQL HTTP 响应是 200(OK)。
要创建WebGraphQlTester
,你需要以下输入之一:
WebTestClient
——作为 HTTP 客户机执行请求,或者针对没有服务器的HTTP处理程序,或者针对活动服务器。WebGraphQlHandler
——通过网络拦截和WebSocket处理程序都使用的网络拦截链执行请求,这实际上是在没有 Web 框架的情况下进行的测试。使用这个的一个原因是订阅。
对于没有服务器的 Spring WebFlux,你可以指向你的 Spring 配置:
ApplicationContext context = ... ;
WebTestClient client =
WebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("/graphql")
.build();
WebGraphQlTester tester = WebGraphQlTester.builder(client).build();
对于没有服务器的 Spring MVC,使用MockMvcWebTestClient
的方法是相同的:
WebApplicationContext context = ... ;
WebTestClient client =
MockMvcWebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("/graphql")
.build();
WebGraphQlTester tester = WebGraphQlTester.builder(client).build();
对运行中的实时服务器进行测试:
WebTestClient client =
WebTestClient.bindToServer()
.baseUrl("http://localhost:8080/graphql")
.build();
WebGraphQlTester tester = WebGraphQlTester.builder(client).build();
WebGraphQlTester
支持设置 HTTP 请求标头和访问 HTTP 响应标头。这对于检查或设置与安全性相关的标题可能很有用。
this.graphQlTester.queryName("{ myQuery }")
.httpHeaders(headers -> headers.setBasicAuth("rob", "..."))
.execute()
.httpHeadersSatisfy(headers -> {
// check response headers
})
.path("myQuery.field1").entity(String.class).isEqualTo("value1")
.path("myQuery.field2").entity(String.class).isEqualTo("value2");
你还可以在构建器级别设置默认的请求标题:
WebGraphQlTester tester = WebGraphQlTester.builder(client)
.defaultHttpHeaders(headers -> headers.setBasicAuth("rob", "..."))
.build();
# 8.3. 查询
下面是一个使用JsonPath (opens new window)提取 GraphQL 响应中所有发布版本的示例查询测试。
String query = "{" +
" project(slug:\"spring-framework\") {" +
" releases {" +
" version" +
" }"+
" }" +
"}";
graphQlTester.query(query)
.execute()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
JSONPath 是相对于响应的“数据”部分的。
还可以在 Classpath 上的"graphql/"
下创建扩展名为.graphql
或.gql
的查询文件,并通过文件名引用它们。例如,给定一个名为projectReleases.graphql
insrc/main/resources/graphql
的文件,其内容如下:
query projectReleases($slug: ID!) {
project(slug: $slug) {
releases {
version
}
}
}
你可以编写相同的测试,如下所示:
graphQlTester.queryName("projectReleases") (1)
.variable("slug", "spring-framework") (2)
.execute()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
1 | 请参阅名为“ProjectReleases”的文件中的查询。 |
---|---|
2 | 设置slug 变量。 |
IntelliJ 的“JS GraphQL”插件支持带有代码补全功能的 GraphQL 查询文件。 |
---|
# 8.4. 错误
当响应中的“错误”键下有错误时,验证将不会成功。
如果有必要忽略错误,请使用错误过滤器Predicate
:
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
相反,如果响应中不存在断言错误,则抛出该断言错误:
graphQlTester.query(query)
.execute()
.errors()
.expect(error -> ...)
.verify()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
或直接检查所有错误,并将其标记为已过滤的:
graphQlTester.query(query)
.execute()
.errors()
.satisfy(errors -> {
// ...
});
如果请求没有任何响应数据(例如,突变),请使用executeAndVerify
而不是execute
来验证响应中没有错误:
graphQlTester.query(query).executeAndVerify();
# 8.5. 订阅
executeSubscription
方法定义了一个特定于订阅的工作流,该工作流将返回一个响应流,而不是单个响应。
要测试订阅,你可以使用GraphQlTester
创建GraphQlService
,它直接调用graphql.GraphQL
,并返回一个响应流:
GraphQlService service = ... ;
GraphQlTester graphQlTester = GraphQlTester.builder(service).build();
Flux<String> result = graphQlTester.query("subscription { greetings }")
.executeSubscription()
.toFlux("greetings", String.class); // decode each response
来自 Project Reactor 的对于验证流是有用的:
Flux<String> result = graphQlTester.query("subscription { greetings }")
.executeSubscription()
.toFlux("greetings", String.class);
StepVerifier.create(result)
.expectNext("Hi")
.expectNext("Bonjour")
.expectNext("Hola")
.verifyComplete();
要测试网络拦截链,你可以使用WebGraphQlHandler
创建WebGraphQlTester
:
GraphQlService service = ... ;
WebGraphQlHandler handler = WebGraphQlHandler.builder(service)
.interceptor((input, next) -> next.handle(input))
.build();
WebGraphQlTester graphQlTester = WebGraphQlTester.builder(handler).build();
目前, Spring for GraphQL 不支持使用 WebSocket 客户端进行测试,并且它不能用于在 WebSocket 请求上对 GraphQL 进行集成测试。
# 9. 样本
Spring 对于 GraphQL 存储库,针对各种场景包含示例应用程序 (opens new window)。
你可以通过克隆此存储库并从你的 IDE 中运行主应用程序类,或者通过在命令行中键入以下内容来运行这些应用程序:
$ ./gradlew :samples:{sample-directory-name}:bootRun