3. 请求执行
ExecutionGraphQlService是调用 GraphQL Java 执行的主要 Spring 抽象
请求。基础传输(例如 HTTP)委托给ExecutionGraphQlService来处理请求。
主要实现DefaultExecutionGraphQlService,配置了GraphQlSource用于访问graphql.GraphQL实例调用。
3.1.GraphQLSource
GraphQlSource是一个合约,用于公开graphql.GraphQL实例也使用它
包括一个构建器 API,用于构建该实例。默认构建器可通过以下方式获得GraphQlSource.schemaResourceBuilder().
Boot Starter 创建此构建器的实例并进一步初始化它
要从可配置位置加载架构文件,
以公开要应用的属性GraphQlSource.Builder,以检测RuntimeWiringConfigurerbean、用于 GraphQL 指标的检测 bean、
和DataFetcherExceptionResolver和SubscriptionExceptionResolver用于异常解决的 bean。如需进一步自定义,您还可以
声明一个GraphQlSourceBuilderCustomizerbean,例如:
@Configuration(proxyBeanMethods = false)
class GraphQlConfig {
@Bean
public GraphQlSourceBuilderCustomizer sourceBuilderCustomizer() {
return (builder) ->
builder.configureGraphQl(graphQlBuilder ->
graphQlBuilder.executionIdProvider(new CustomExecutionIdProvider()));
}
}
3.1.1. 模式资源
GraphQlSource.Builder可以配置一个或多个Resource实例
解析并合并在一起。这意味着模式文件几乎可以从任何
位置。
默认情况下,启动Starters会查找扩展名为
“.graphqls”或“.gqls”classpath:graphql/**,这通常是src/main/resources/graphql.您还可以使用文件系统位置或任何位置
由弹簧支持Resource层次结构,包括自定义实现
从远程位置、存储或内存加载架构文件。
用classpath*:graphql/**/跨多个类路径查找架构文件
位置,例如跨多个模块。 |
3.1.2. 模式创建
默认情况下,GraphQlSource.Builder使用 GraphQL JavaSchemaGenerator创建graphql.schema.GraphQLSchema.这适用于典型用途,但如果您需要使用
不同的生成器,例如对于联合,您可以注册一个schemaFactory回调:
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.schemaFactory((typeDefinitionRegistry, runtimeWiring) -> {
// create GraphQLSchema
})
GraphQlSource 部分解释了如何使用 Spring Boot 进行配置。
有关 Apollo Federation 的示例,请参阅 federation-jvm-spring-example。
3.1.3. 模式遍历
您可以注册一个graphql.schema.GraphQLTypeVisitor通过builder.schemaResources(..).typeVisitors(..)如果要在
它被创建,并可能对GraphQLCodeRegistry.请记住,
但是,此类访问者无法更改架构。如果需要对架构进行更改,请参阅架构转换。
3.1.4. 模式转换
您可以注册一个graphql.schema.GraphQLTypeVisitor通过builder.schemaResources(..).typeVisitorsToTransformSchema(..)如果要遍历
并在创建架构后对其进行转换,并对架构进行更改。记住
这比 Schema Traversal 更昂贵,因此通常
除非需要进行架构更改,否则更喜欢遍历而不是转换。
3.1.5.RuntimeWiringConfigurer
您可以使用RuntimeWiringConfigurer注册:
-
自定义标量类型。
-
指令处理代码。
-
TypeResolver,如果您需要覆盖默认值TypeResolver对于类型。 -
DataFetcher对于字段,尽管大多数应用程序将简单地配置AnnotatedControllerConfigurer,它检测已注释的DataFetcher处理程序方法。 Boot Starter 将AnnotatedControllerConfigurer默认情况下。
| GraphQL Java,服务器应用程序仅使用 Jackson 进行数据映射的序列化。 客户端输入被解析为映射。服务器输出将根据字段选择集组合到地图中。 这意味着您不能依赖 Jackson 序列化/反序列化注释。 相反,您可以使用自定义标量类型。 |
Boot Starter 检测类型为RuntimeWiringConfigurer和
将它们注册在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>.这允许您添加任何
然后按顺序调用的工厂数。
3.1.6. 默认值TypeResolver
GraphQlSource.Builder寄存 器ClassNameTypeResolver作为默认值TypeResolver用于尚未进行此类注册的 GraphQL 接口和联合
通过RuntimeWiringConfigurer.目的
一个TypeResolver在 GraphQL 中,Java 是确定值的 GraphQL 对象类型
从DataFetcher用于 GraphQL 接口或联合字段。
ClassNameTypeResolver尝试将值的简单类名与 GraphQL 匹配
对象类型,如果不成功,它还会导航其超级类型,包括
基类和接口,寻找匹配项。ClassNameTypeResolver提供
配置名称提取功能的选项以及Class到 GraphQL 对象类型
应该有助于覆盖更多极端情况的名称映射:
GraphQlSource.Builder builder = ...
ClassNameTypeResolver classNameTypeResolver = new ClassNameTypeResolver();
classNameTypeResolver.setClassNameExtractor((klass) -> {
// Implement Custom ClassName Extractor here
});
builder.defaultTypeResolver(classNameTypeResolver);
GraphQlSource 部分解释了如何使用 Spring Boot 进行配置。
3.1.7.作缓存
GraphQL Java 必须在执行作之前对其进行解析和验证。这可能会影响
性能显着。为了避免需要重新解析和验证,应用程序可以
配置一个PreparsedDocumentProvider缓存和重用文档实例。GraphQL Java 文档提供了更多详细信息
通过PreparsedDocumentProvider.
在 Spring GraphQL 中,您可以注册一个PreparsedDocumentProvider通过GraphQlSource.Builder#configureGraphQl:
.
// Typically, accessed through Spring Boot's GraphQlSourceBuilderCustomizer
GraphQlSource.Builder builder = ...
// Create provider
PreparsedDocumentProvider provider = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.configureGraphQl(graphQLBuilder -> graphQLBuilder.preparsedDocumentProvider(provider))
GraphQlSource 部分解释了如何使用 Spring Boot 进行配置。
3.1.8. 指令
GraphQL 语言支持“描述替代运行时执行和 GraphQL 文档中的类型验证行为“。指令类似于 Java,但在 GraphQL 文档中的类型、字段、片段和作上声明。
GraphQL Java 提供了SchemaDirectiveWiring帮助应用程序检测的契约
和处理指令。有关更多详细信息,请参阅
GraphQL Java 文档。
在 Spring GraphQL 中,您可以注册一个SchemaDirectiveWiring通过RuntimeWiringConfigurer.启动Starters检测到
这样的 bean,所以你可能有类似的东西:
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return builder -> builder.directiveWiring(new MySchemaDirectiveWiring());
}
}
| 有关指令支持的示例,请查看 Graphql Java 库的扩展验证。 |
3.2. 响应式DataFetcher
默认值GraphQlSourcebuilder 启用了对DataFetcher返回Mono或Flux它使这些调整为CompletableFuture哪里Flux值是聚合的
并转换为 List,除非该请求是 GraphQL 订阅请求,
在这种情况下,返回值仍为 Reactive StreamsPublisher用于流媒体
GraphQL 响应。
反应性DataFetcher可以依赖于对从
传输层,例如来自 WebFlux 请求处理,请参阅 WebFlux 上下文。
3.3. 上下文传播
Spring for GraphQL 支持通过 GraphQL Java 透明地传播上下文,并DataFetcher和其他组件 it
调用。这包括两者ThreadLocalSpring MVC 请求处理中的上下文
螺纹和反应器Context来自 WebFlux 处理管道。
3.3.1. WebMvc
一个DataFetcherGraphQL Java 调用的其他组件可能并不总是在
与 Spring MVC 处理程序相同的线程,例如,如果异步WebGraphQlInterceptor或DataFetcher切换到
不同的线程。
Spring for GraphQL 支持传播ThreadLocalServlet 容器中的值
线程到线程 aDataFetcher以及 GraphQL Java 调用的其他组件
执行。为此,应用程序需要实现io.micrometer.context.ThreadLocalAccessor对于一个ThreadLocal感兴趣的值:
public class RequestAttributesAccessor implements ThreadLocalAccessor<RequestAttributes> {
@Override
public Object key() {
return RequestAttributesAccessor.class.getName();
}
@Override
public RequestAttributes getValue() {
return RequestContextHolder.getRequestAttributes();
}
@Override
public void setValue(RequestAttributes attributes) {
RequestContextHolder.setRequestAttributes(attributes);
}
@Override
public void reset() {
RequestContextHolder.resetRequestAttributes();
}
}
您可以注册一个ThreadLocalAccessor使用全局启动时手动ContextRegistry实例,可通过以下方式访问io.micrometer.context.ContextRegistry#getInstance().您也可以注册它
自动通过java.util.ServiceLoader机制。
3.3.2. WebFlux
一个反应性的DataFetcher可以依赖于对 Reactor 上下文的访问,即
源自 WebFlux 请求处理链。这包括 Reactor 上下文
由 WebGraphQlInterceptor 组件添加。
3.4. 异常解决
GraphQL Java 应用程序可以注册一个DataFetcherExceptionHandler决定如何
在 GraphQL 响应的“错误”部分中表示来自数据层的异常。
Spring for GraphQL 有一个内置的DataFetcherExceptionHandler配置为使用
默认情况下GraphQLSource架构工人。它允许应用程序注册
一个或多个弹簧DataFetcherExceptionResolver按顺序调用的组件
直到一个人解决Exception到(可能是空的)列表graphql.GraphQLError对象。
DataFetcherExceptionResolver是一个异步契约。对于大多数实现,它
将足以延长DataFetcherExceptionResolverAdapter并覆盖
其之一resolveToSingleError或resolveToMultipleErrors方法
同步解决异常。
一个GraphQLError可以通过graphql.ErrorClassification.
在 Spring GraphQL 中,您还可以通过ErrorType其中有以下共同点
应用程序可用于对错误进行分类的分类:
-
BAD_REQUEST -
UNAUTHORIZED -
FORBIDDEN -
NOT_FOUND -
INTERNAL_ERROR
如果异常仍未解决,则默认情况下将其分类为INTERNAL_ERROR使用包含类别名称和executionId从DataFetchingEnvironment.该消息故意不透明以避免泄漏
实现详细信息。应用程序可以使用DataFetcherExceptionResolver自定义
错误详细信息。
未解决的异常将与executionId关联
发送到客户端的错误。已解决的异常记录在 DEBUG 级别。
3.4.1. 请求异常
GraphQL Java 引擎在解析请求时可能会遇到验证或其他错误
这反过来又阻止了请求执行。在这种情况下,响应包含一个
“data”键与null以及一个或多个全局的请求级“错误”,即不是
具有字段路径。
DataFetcherExceptionResolver无法处理此类全局错误,因为它们被引发
在执行开始之前和任何执行之前DataFetcher被调用。应用程序可以使用
传输级拦截器,用于检查和转换ExecutionResult.
请参阅下面的示例WebGraphQlInterceptor.
3.5. 批量加载
给定一个Book及其Author,我们可以创建一个DataFetcher对于一本书和另一本书对于它的作者。这允许选择有或没有作者的书籍,但这意味着书籍并且作者不会一起加载,这在查询多个书籍时效率尤其低,因为每本书的作者都是单独加载的。这被称为 N+1 选择 问题。
3.5.1.DataLoader
GraphQL Java 提供了一个DataLoader批量加载相关实体的机制。您可以在 GraphQL Java 文档中找到完整的详细信息。下面是一个它的工作原理摘要:
-
注册
DataLoader在DataLoaderRegistry可以加载给定唯一键的实体。 -
DataFetcher的可以访问DataLoader的,并使用它们按 id 加载实体。 -
一个
DataLoader通过返回 future 来延迟加载,以便可以批量完成。 -
DataLoader维护加载实体的每个请求缓存,可以进一步提高效率。
3.5.2.BatchLoaderRegistry
GraphQL Java 中的完整批处理加载机制需要实现 几个BatchLoader接口,然后将它们包装并注册为DataLoaders
在DataLoaderRegistry.
Spring GraphQL 中的 API 略有不同。对于注册,只有一个,
中央BatchLoaderRegistry公开工厂方法和构建器以创建和
注册任意数量的批量加载函数:
@Configuration
public class MyConfig {
public MyConfig(BatchLoaderRegistry registry) {
registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
// return Mono<Map<Long, Author>
});
// more registrations ...
}
}
Boot Starter 声明BatchLoaderRegistry您可以注入的 bean
您的配置,如上所示,或按顺序插入任何组件(例如控制器)
注册批量加载函数。反过来,BatchLoaderRegistry被注入DefaultExecutionGraphQlService确保DataLoader每个请求的注册数。
默认情况下,DataLoadername 基于目标实体的类名。
这允许@SchemaMapping方法来声明具有泛型类型的 DataLoader 参数,以及
无需指定名称。但是,可以通过BatchLoaderRegistry构建器,如有必要,以及其他DataLoaderOptions.
配置默认值DataLoaderOptions全局,用作任何
注册,您可以覆盖 Boot 的BatchLoaderRegistrybean 并使用
为DefaultBatchLoaderRegistry接受Supplier<DataLoaderOptions>.
在许多情况下,在加载相关实体时,可以使用@BatchMapping控制器方法,这是一种快捷方式
为和取代需要使用BatchLoaderRegistry和DataLoader径直。
BatchLoaderRegistry还提供其他重要的好处。它支持访问
一样GraphQLContext从 batch loading 函数和 from@BatchMapping方法
并确保上下文传播到它们。这就是需要应用的原因
使用它。可以自己执行DataLoader直接注册,但
此类注册将放弃上述好处。
3.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("...");
// ...