4. 请求执行

ExecutionGraphQlService是调用 GraphQL Java 执行的主要 Spring 抽象 请求。基础传输(如 Server Transports)委托 to 来处理请求。ExecutionGraphQlServicespring-doc.cn

主实现 配置了 用于访问要调用的实例。DefaultExecutionGraphQlServiceGraphQlSourcegraphql.GraphQLspring-doc.cn

4.1.GraphQLSource

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

默认构建器(可通过 访问)支持反应式 DataFetcher上下文传播异常解决GraphQlSourceGraphQlSource.schemaResourceBuilder()spring-doc.cn

Spring Boot Starters通过默认值初始化实例,并启用 以下内容:GraphQlSourceGraphQlSource.Builderspring-doc.cn

对于进一步的自定义,您可以声明自己的 bean; 例如,要配置您自己的 :GraphQlSourceBuilderCustomizerExecutionIdProviderspring-doc.cn

@Configuration(proxyBeanMethods = false)
class GraphQlConfig {

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

4.1.1. Schema 资源

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

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

用于跨多个 Classpath 查找 schema 文件 位置,例如跨多个模块。classpath*:graphql/**/

4.1.2. Schema 创建

默认情况下,使用 GraphQL Java 来 创建 .这适用于大多数应用程序,但如果 必要时,您可以通过构建器挂接到 schema 创建中:GraphQlSource.BuilderGraphQLSchemaGeneratorgraphql.schema.GraphQLSchemaspring-doc.cn

GraphQlSource.Builder builder = ...

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

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

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

4.1.3.RuntimeWiringConfigurer

您可以使用以下方法进行注册:RuntimeWiringConfigurerspring-doc.cn

  • 自定义标量类型。spring-doc.cn

  • 处理代码的指令。spring-doc.cn

  • TypeResolver,如果需要覆盖类型的 Default TypeResolverspring-doc.cn

  • DataFetcher对于字段,尽管大多数应用程序只会 configure ,它检测带注释的处理程序方法。 Spring Boot Starters默认添加AnnotatedControllerConfigurerDataFetcherAnnotatedControllerConfigurerspring-doc.cn

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

Spring Boot Starters检测 type 为 在 .这意味着在大多数情况下,您将拥有 类似于 this 的配置:RuntimeWiringConfigurerGraphQlSource.Builderspring-doc.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));
    }
}

如果您需要添加 ,例如,进行注册时要考虑 模式定义中,实现同时接受 和 output 的替代方法。这允许您添加任何 然后按顺序调用的工厂数。WiringFactoryconfigureRuntimeWiring.BuilderList<WiringFactory>spring-doc.cn

4.1.4. 默认TypeResolver

GraphQlSource.Builderregisters 作为默认值,用于尚未进行此类注册的 GraphQL 接口和联合 通过 RuntimeWiringConfigurer。目的 a 用于确定值的 GraphQL 对象类型 从 for a GraphQL Interface 或 Union 字段返回。ClassNameTypeResolverTypeResolverTypeResolverDataFetcherspring-doc.cn

ClassNameTypeResolver尝试将值的简单类名与 GraphQL 匹配 Object 类型,如果不成功,它还会导航其超类型,包括 基类和接口,寻找匹配项。 提供了一个 选项来配置名称提取函数以及 GraphQL 对象类型 名称映射应该有助于涵盖更多极端情况:ClassNameTypeResolverClassspring-doc.cn

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

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

4.1.5. 操作缓存

GraphQL Java 必须在执行操作之前对其进行解析验证。这可能会影响 性能显着。为避免需要重新分析和验证,应用程序可以 配置 a 来缓存和重用 Document 实例。GraphQL Java 文档提供了有关以下内容的更多详细信息 查询缓存。PreparsedDocumentProviderPreparsedDocumentProviderspring-doc.cn

在 Spring GraphQL 中,您可以通过: .PreparsedDocumentProviderGraphQlSource.Builder#configureGraphQlspring-doc.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.cn

4.1.6. 指令

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

GraphQL Java 提供了帮助应用程序检测的协定 和 handle 指令。有关更多详细信息,请参阅 GraphQL Java 文档。SchemaDirectiveWiringspring-doc.cn

在 Spring GraphQL 中,你可以通过RuntimeWiringConfigurer注册一个。Spring Boot Starters检测到 这样的 bean,所以你可能会有这样的东西:SchemaDirectiveWiringspring-doc.cn

@Configuration
public class GraphQlConfig {

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

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

4.2. 反应式DataFetcher

默认构建器支持返回 a 或将其调整为 where 值聚合 并转换为 List,除非请求是 GraphQL 订阅请求, 在这种情况下,返回值仍然是用于流式处理的 Reactive Streams GraphQL 响应。GraphQlSourceDataFetcherMonoFluxCompletableFutureFluxPublisherspring-doc.cn

响应式可以依赖从 传输层,例如从 WebFlux 请求处理,请参阅 WebFlux 上下文DataFetcherspring-doc.cn

4.3. 上下文传播

Spring for GraphQL 支持通过 GraphQL Java 透明地将上下文从服务器传输传播到它的其他组件 调用。这包括来自 Spring MVC 请求处理的上下文 thread 和 Reactor 从 WebFlux 处理管道。DataFetcherThreadLocalContextspring-doc.cn

4.3.1. WebMvc

GraphQL Java 调用的 A 和其他组件可能并不总是在 与 Spring MVC 处理程序相同的线程,例如,如果异步 WebGraphQlInterceptor 或切换到 不同的线程。DataFetcherDataFetcherspring-doc.cn

Spring for GraphQL 支持从 Servlet 容器传播值 线程添加到线程 a 和其他组件中,GraphQL Java 调用到 执行时间。为此,应用程序需要创建一个来提取感兴趣的值:ThreadLocalDataFetcherThreadLocalAccessorThreadLocalspring-doc.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();
    }

}

A 可以在 WebGraphHandler 构建器中注册。Boot Starters会检测此类型的 bean 并自动为 Spring MVC 应用程序,请参阅 Web 端点部分。ThreadLocalAccessorspring-doc.cn

4.3.2. WebFlux 网络

响应式 DataFetcher 可以依赖于对 Reactor 上下文的访问,该上下文 源自 WebFlux 请求处理链。这包括 Reactor 上下文 由 WebGraphQlInterceptor 组件添加。spring-doc.cn

4.4. 异常解决

GraphQL Java 应用程序可以注册 a 来决定如何 表示 GraphQL 响应的 “errors” 部分中数据层的异常。DataFetcherExceptionHandlerspring-doc.cn

Spring for GraphQL 有一个内置功能,该内置功能已配置为使用 通过默认的 GraphQLSource 构建器。它允许应用程序注册 按顺序调用的一个或多个 Spring 组件 直到将 解析为对象列表(可能为空)。DataFetcherExceptionHandlerDataFetcherExceptionResolverExceptiongraphql.GraphQLErrorspring-doc.cn

DataFetcherExceptionResolver是一个异步合约。对于大多数实现,它 就足以扩展和覆盖 其 or 方法之一 同步解决异常。DataFetcherExceptionResolverAdapterresolveToSingleErrorresolveToMultipleErrorsspring-doc.cn

可以通过 A 分配给类别。 在 Spring GraphQL 中,您还可以通过 which 分配具有以下常见 应用程序可用于对错误进行分类的分类:GraphQLErrorgraphql.ErrorClassificationErrorTypespring-doc.cn

如果异常仍未解决,则默认情况下,该异常被归类为包含类别名称和 from 的通用消息。该消息故意不透明以避免泄漏 实现细节。应用程序可以使用 进行自定义 错误详细信息。INTERNAL_ERRORexecutionIdDataFetchingEnvironmentDataFetcherExceptionResolverspring-doc.cn

未解决的异常将记录在 ERROR 级别以及 to correlate 发送到客户端的错误。已解决的异常记录在 DEBUG 级别。executionIdspring-doc.cn

4.4.1. 请求异常

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

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

4.4.2. 订阅例外

的 for a subscription 请求可能会完成并显示错误信号,在这种情况下 底层传输(例如 WebSocket)发送带有列表的最终 “error” 类型消息 的 GraphQL 错误。Publisherspring-doc.cn

DataFetcherExceptionResolver无法解决订阅 中的错误 由于数据仅创建初始。之后, transport 订阅 ,然后可能会完成并显示错误。PublisherDataFetcherPublisherPublisherspring-doc.cn

应用程序可以注册 a 以解析 exceptions 来解决这些问题为 GraphQL 错误 以发送到客户端。SubscriptionExceptionResolverPublisherspring-doc.cn

4.5. 批量加载

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

4.5.1.DataLoader

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

  1. Register 的 ,可以加载实体,给定唯一键。DataLoaderDataLoaderRegistryspring-doc.cn

  2. DataFetcher可以访问 并使用它们按 ID 加载实体。DataLoaderspring-doc.cn

  3. A 通过返回 future 来延迟加载,以便可以批量完成。DataLoaderspring-doc.cn

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

4.5.2.BatchLoaderRegistry

GraphQL Java 中的完整批处理加载机制需要实现以下之一 several 接口,然后将它们包装并注册为 S 名称位于 .BatchLoaderDataLoaderDataLoaderRegistryspring-doc.cn

Spring GraphQL 中的 API 略有不同。对于注册,只有一个 Central 公开工厂方法和用于 create 和 注册任意数量的批量加载函数:BatchLoaderRegistryspring-doc.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声明了一个可以注入的 bean 您的配置,如上所示,或按顺序放入任何组件(如控制器) 注册批量加载函数。反过来,它被注入到确保每个请求的注册的位置。BatchLoaderRegistryBatchLoaderRegistryDefaultExecutionGraphQlServiceDataLoaderspring-doc.cn

默认情况下,该名称基于目标实体的类名。 这允许方法使用泛型类型声明 DataLoader 参数,并且 无需指定名称。但是,如有必要,可以通过构建器以及其他选项自定义名称。DataLoader@SchemaMappingBatchLoaderRegistryDataLoaderspring-doc.cn

在许多情况下,在加载相关实体时,您可以使用 @BatchMapping 控制器方法,这是一种快捷方式 for 和 replace 需要使用 和 直接。 S 还提供其他重要的好处。它支持访问 与 batch loading 函数和 from 方法相同, 以及确保对它们的上下文传播。这就是预期应用的原因 以使用它。可以直接执行自己的注册,但 此类注册将放弃上述好处。BatchLoaderRegistryDataLoaderBatchLoaderRegistryGraphQLContext@BatchMappingDataLoaderspring-doc.cn

4.5.3. 测试 Batch Load

首先在 上执行注册 :BatchLoaderRegistryDataLoaderRegistryspring-doc.cn

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

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

现在,您可以按如下方式访问和测试单个 :DataLoaderspring-doc.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("...");
// ...