4. 请求执行

ExecutionGraphQlService是调用 GraphQL Java 执行的主要 Spring 抽象 请求。基础传输(如服务器传输)委托给ExecutionGraphQlService来处理请求。spring-doc.cadn.net.cn

主要实现DefaultExecutionGraphQlService,配置了GraphQlSource用于访问graphql.GraphQL实例调用。spring-doc.cadn.net.cn

4.1.GraphQLSource

GraphQlSource是一个核心的 Spring 抽象,用于访问graphql.GraphQL用于请求执行的实例。它提供了一个构建器 API 初始化 GraphQL Java 并构建一个GraphQlSource.spring-doc.cadn.net.cn

默认值GraphQlSource构建器,可通过以下方式访问GraphQlSource.schemaResourceBuilder(),启用对反应性的DataFetcher上下文传播异常解决spring-doc.cadn.net.cn

Spring Boot Starters初始化一个GraphQlSource实例通过默认GraphQlSource.Builder并且还使 以下内容:spring-doc.cadn.net.cn

如需进一步自定义,您可以声明自己的GraphQlSourceBuilderCustomizer豆; 例如,用于配置您自己的ExecutionIdProvider:spring-doc.cadn.net.cn

@Configuration(proxyBeanMethods = false)
class GraphQlConfig {

    @Bean
    public GraphQlSourceBuilderCustomizer sourceBuilderCustomizer() {
        return (builder) ->
                builder.configureGraphQl(graphQlBuilder ->
                        graphQlBuilder.executionIdProvider(new CustomExecutionIdProvider()));
    }
}

4.1.1. 模式资源

GraphQlSource.Builder可以配置一个或多个Resource实例 解析并合并在一起。这意味着模式文件几乎可以从任何 位置。spring-doc.cadn.net.cn

默认情况下,Spring Boot Starters会查找扩展名为 “.graphqls”或“.gqls”classpath:graphql/**,这通常是src/main/resources/graphql.您还可以使用文件系统位置或任何位置 由弹簧支持Resource层次结构,包括自定义实现 从远程位置、存储或内存加载架构文件。spring-doc.cadn.net.cn

classpath*:graphql/**/跨多个类路径查找架构文件 位置,例如跨多个模块。

4.1.2. 模式创建

默认情况下,GraphQlSource.Builder使用 GraphQL JavaGraphQLSchemaGenerator自 创建graphql.schema.GraphQLSchema.这适用于大多数应用程序,但如果 必要时,您可以通过构建器挂接到架构创建:spring-doc.cadn.net.cn

GraphQlSource.Builder builder = ...

builder.schemaResources(..)
        .configureRuntimeWiring(..)
        .schemaFactory((typeDefinitionRegistry, runtimeWiring) -> {
            // create GraphQLSchema
        })

这样做的主要原因是通过联合库创建架构。spring-doc.cadn.net.cn

GraphQlSource 部分解释了如何使用 Spring Boot 进行配置。spring-doc.cadn.net.cn

4.1.3.RuntimeWiringConfigurer

您可以使用RuntimeWiringConfigurer注册:spring-doc.cadn.net.cn

与 Web 框架不同,GraphQL 不使用 Jackson 注释来驱动 JSON 序列化/反序列化。 自定义数据类型及其序列化必须描述为标量

Spring Boot Starters检测类型为RuntimeWiringConfigurer和 将它们注册在GraphQlSource.Builder.这意味着在大多数情况下,您将拥有 配置中如下所示:spring-doc.cadn.net.cn

@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>.这允许您添加任何 然后按顺序调用的工厂数。spring-doc.cadn.net.cn

4.1.4. 默认值TypeResolver

GraphQlSource.Builder寄存 器ClassNameTypeResolver作为默认值TypeResolver用于尚未进行此类注册的 GraphQL 接口和联合通过RuntimeWiringConfigurer. 目的 一个TypeResolver在 GraphQL 中,Java 是确定值的 GraphQL 对象类型从DataFetcher用于 GraphQL 接口或联合字段。spring-doc.cadn.net.cn

ClassNameTypeResolver尝试将值的简单类名与 GraphQLObject Type 匹配,如果不成功,它还会导航其超级类型,包括基类和接口,寻找匹配项。ClassNameTypeResolver提供 配置名称提取功能的选项以及Class到 GraphQL 对象类型 应该有助于覆盖更多极端情况的名称映射:spring-doc.cadn.net.cn

GraphQlSource.Builder builder = ...
ClassNameTypeResolver classNameTypeResolver = new ClassNameTypeResolver();
classNameTypeResolver.setClassNameExtractor((klass) -> {
    // Implement Custom ClassName Extractor here
});
builder.defaultTypeResolver(classNameTypeResolver);

GraphQlSource 部分解释了如何使用 Spring Boot 进行配置。spring-doc.cadn.net.cn

4.1.5.作缓存

GraphQL Java 必须在执行作之前对其进行解析验证。这可能会影响 性能显着。为了避免需要重新解析和验证,应用程序可以 配置一个PreparsedDocumentProvider缓存和重用文档实例。GraphQL Java 文档提供了更多详细信息 通过PreparsedDocumentProvider.spring-doc.cadn.net.cn

在 Spring GraphQL 中,您可以注册一个PreparsedDocumentProvider通过GraphQlSource.Builder#configureGraphQl: .spring-doc.cadn.net.cn

// 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 进行配置。spring-doc.cadn.net.cn

4.1.6. 指令

GraphQL 语言支持“描述替代运行时执行和 GraphQL 文档中的类型验证行为“。指令类似于 Java,但在 GraphQL 文档中的类型、字段、片段和作上声明。spring-doc.cadn.net.cn

GraphQL Java 提供了SchemaDirectiveWiring帮助应用程序检测的契约 和处理指令。有关更多详细信息,请参阅 GraphQL Java 文档。spring-doc.cadn.net.cn

在 Spring GraphQL 中,您可以注册一个SchemaDirectiveWiring通过RuntimeWiringConfigurer.Spring Boot Starters检测到 这样的 bean,所以你可能有类似的东西:spring-doc.cadn.net.cn

@Configuration
public class GraphQlConfig {

     @Bean
     public RuntimeWiringConfigurer runtimeWiringConfigurer() {
          return builder -> builder.directiveWiring(new MySchemaDirectiveWiring());
     }

}
有关指令支持的示例,请查看 Graphql Java 库的扩展验证

4.2. 响应式DataFetcher

默认值GraphQlSourcebuilder 启用了对DataFetcher返回MonoFlux它使这些调整为CompletableFuture哪里Flux值是聚合的 并转换为 List,除非该请求是 GraphQL 订阅请求, 在这种情况下,返回值仍为 Reactive StreamsPublisher用于流媒体 GraphQL 响应。spring-doc.cadn.net.cn

反应性DataFetcher可以依赖于对从 传输层,例如来自 WebFlux 请求处理,请参阅 WebFlux 上下文spring-doc.cadn.net.cn

4.3. 上下文传播

Spring for GraphQL 支持通过 GraphQL Java 透明地传播上下文DataFetcher和其他组件 it 调用。 这包括两者ThreadLocal上下文来自 Spring MVC 请求处理线程和 ReactorContext来自 WebFlux 处理管道。spring-doc.cadn.net.cn

4.3.1. WebMvc

一个DataFetcherGraphQL Java 调用的其他组件可能并不总是在 与 Spring MVC 处理程序相同的线程,例如,如果异步WebGraphQlInterceptorDataFetcher切换到 不同的线程。spring-doc.cadn.net.cn

Spring for GraphQL 支持传播ThreadLocalServlet 容器中的值 线程到线程 aDataFetcher以及 GraphQL Java 调用的其他组件 执行。为此,应用程序需要创建一个ThreadLocalAccessor提取ThreadLocal感兴趣的值:spring-doc.cadn.net.cn

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

}

一个ThreadLocalAccessor可以在 WebGraphHandler 构建器中注册。Boot Starters检测这种类型的 bean 并自动将它们注册为 Spring MVC 应用程序,请参阅 Web 端点部分。spring-doc.cadn.net.cn

4.3.2. WebFlux

一个反应性的DataFetcher可以依赖于对 Reactor 上下文的访问,即 源自 WebFlux 请求处理链。这包括 Reactor 上下文 由 WebGraphQlInterceptor 组件添加。spring-doc.cadn.net.cn

4.4. 异常解决

GraphQL Java 应用程序可以注册一个DataFetcherExceptionHandler决定如何 在 GraphQL 响应的“错误”部分中表示来自数据层的异常。spring-doc.cadn.net.cn

Spring for GraphQL 有一个内置的DataFetcherExceptionHandler配置为使用 默认情况下GraphQLSource架构工人。它允许应用程序注册 一个或多个弹簧DataFetcherExceptionResolver按顺序调用的组件 直到一个人解决Exception到(可能是空的)列表graphql.GraphQLError对象。spring-doc.cadn.net.cn

DataFetcherExceptionResolver是一个异步契约。对于大多数实现,它 将足以延长DataFetcherExceptionResolverAdapter并覆盖 其之一resolveToSingleErrorresolveToMultipleErrors方法 同步解决异常。spring-doc.cadn.net.cn

一个GraphQLError可以通过graphql.ErrorClassification. 在 Spring GraphQL 中,您还可以通过ErrorType其中有以下共同点 应用程序可用于对错误进行分类的分类:spring-doc.cadn.net.cn

如果异常仍未解决,则默认情况下将其分类为INTERNAL_ERROR使用包含类别名称和executionIdDataFetchingEnvironment.该消息故意不透明以避免泄漏 实现详细信息。应用程序可以使用DataFetcherExceptionResolver自定义 错误详细信息。spring-doc.cadn.net.cn

未解决的异常将与executionId关联 发送到客户端的错误。已解决的异常记录在 DEBUG 级别。spring-doc.cadn.net.cn

4.4.1. 请求异常

GraphQL Java 引擎在解析请求时可能会遇到验证或其他错误 这反过来又阻止了请求执行。在这种情况下,响应包含一个 “data”键与null以及一个或多个全局的请求级“错误”,即不是 具有字段路径。spring-doc.cadn.net.cn

DataFetcherExceptionResolver无法处理此类全局错误,因为它们被引发 在执行开始之前和任何执行之前DataFetcher被调用。应用程序可以使用 传输级拦截器,用于检查和转换ExecutionResult. 请参阅下面的示例WebGraphQlInterceptor.spring-doc.cadn.net.cn

4.4.2. 订阅例外

Publisher对于订阅请求,可能会以错误信号完成,在这种情况下 底层传输(例如 WebSocket)发送带有列表的最终“错误”类型消息 的 GraphQL 错误。spring-doc.cadn.net.cn

DataFetcherExceptionResolver无法解决订阅中的错误Publisher, 自数据DataFetcher仅创建Publisher最初。之后, transport 订阅Publisher然后可能会以错误完成。spring-doc.cadn.net.cn

应用程序可以注册SubscriptionExceptionResolver为了解决 订阅的例外情况Publisher为了将这些错误解决为 GraphQL 错误 发送给客户端。spring-doc.cadn.net.cn

4.5. 批量加载

给定一个Book及其Author,我们可以创建一个DataFetcher为一本书和另一本书 对于它的作者。这允许选择有或没有作者的书籍,但这意味着书籍 并且作者不会一起加载,这在查询多个 books 作为每本书的作者被单独加载。这称为 N+1 选择 问题。spring-doc.cadn.net.cn

4.5.1.DataLoader

GraphQL Java 提供了一个DataLoader相关实体批量加载的机制。 您可以在 GraphQL Java 文档中找到完整的详细信息。下面是一个 工作原理摘要:spring-doc.cadn.net.cn

  1. 注册DataLoaderDataLoaderRegistry可以加载给定唯一键的实体。spring-doc.cadn.net.cn

  2. DataFetcher的可以访问DataLoader的,并使用它们按 id 加载实体。spring-doc.cadn.net.cn

  3. 一个DataLoader通过返回 future 来延迟加载,以便可以批量完成。spring-doc.cadn.net.cn

  4. DataLoader的维护加载实体的每个请求缓存,这些实体可以进一步 提高效率。spring-doc.cadn.net.cn

4.5.2.BatchLoaderRegistry

GraphQL Java 中的完整批处理加载机制需要实现 几个BatchLoader接口,然后将它们包装并注册为DataLoaders 在DataLoaderRegistry.spring-doc.cadn.net.cn

Spring GraphQL 中的 API 略有不同。对于注册,只有一个, 中央BatchLoaderRegistry公开工厂方法和构建器以创建和 注册任意数量的批量加载函数:spring-doc.cadn.net.cn

@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 Boot Starters声明一个BatchLoaderRegistry您可以注入的 bean 您的配置,如上所示,或按顺序插入任何组件(例如控制器) 注册批量加载函数。反过来,BatchLoaderRegistry被注入DefaultExecutionGraphQlService确保DataLoader每个请求的注册数。spring-doc.cadn.net.cn

默认情况下,DataLoadername 基于目标实体的类名。 这允许@SchemaMapping方法来声明具有泛型类型的 DataLoader 参数,以及 无需指定名称。但是,可以通过BatchLoaderRegistry构建器,如有必要,以及其他DataLoader选项。spring-doc.cadn.net.cn

在许多情况下,在加载相关实体时,可以使用@BatchMapping控制器方法,这是一种快捷方式 为和取代需要使用BatchLoaderRegistryDataLoader径直。 sBatchLoaderRegistry还提供其他重要的好处。它支持访问 一样GraphQLContext从 batch loading 函数和 from@BatchMapping方法 并确保上下文传播到它们。这就是需要应用的原因 使用它。可以自己执行DataLoader直接注册,但 此类注册将放弃上述好处。spring-doc.cadn.net.cn

4.5.3. 测试批量加载

首先拥有BatchLoaderRegistryDataLoaderRegistry:spring-doc.cadn.net.cn

BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry();
// perform registrations...

DataLoaderRegistry dataLoaderRegistry = DataLoaderRegistry.newRegistry().build();
batchLoaderRegistry.registerDataLoaders(dataLoaderRegistry, graphQLContext);

现在您可以访问和测试单个DataLoader如下:spring-doc.cadn.net.cn

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("...");
// ...