# 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)小节以获取详细信息,或检查其包含的GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration以获取实际配置。

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)部分,或检查其包含的GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration以获取实际配置。

Spring for GraphQL 存储库包含一个 WebFluxWebSocket sample (opens new window)应用程序。

# 3.3. 网络拦截

HTTPWebSocket传输处理程序委托给公共 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)小节以获取详细信息,或检查其包含的GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration以获取实际配置。

# 4. 请求执行

GraphQlService是调用 GraphQL Java 执行请求的主要 Spring 抽象。底层传输,例如网络传输,委托给GraphQlService来处理请求。

主要的实现ExecutionGraphQlService是围绕graphql.GraphQL调用的一个很薄的外观。它配置了GraphQlSource以访问graphql.GraphQL实例。

# 4.1. GraphQLSource

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

默认的GraphQlSourceBuilder 可通过GraphQlSource.builder()访问,支持[activeDataFetcher](#execution-active-datafetcher)、上下文传播异常解决

Spring bootstarter (opens new window)通过默认的GraphQlSource.Builder初始化GraphQlSource实例,并启用以下功能:

# 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.BuilderClassNameTypeResolver注册为默认的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

默认的GraphQlSourceBuilder 使对DataFetcher的支持返回MonoFlux,这将这些支持调整为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并覆盖其同步解决异常的resolveToSingleErrorresolveToMultipleErrors方法之一就足够了。

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)中找到完整的详细信息。以下是其工作原理的摘要:

  1. DataLoaderRegistry中注册DataLoader的可以加载实体的项,给定唯一的键。

  2. DataFetcher的可以访问DataLoader的,并使用它们通过 ID 加载实体。

  3. aDataLoader通过返回 future 来延迟加载,因此可以在批处理中完成。

  4. DataLoader的每请求保持一个加载实体的缓存,这可以进一步提高效率。

# 4.5.2. BatchLoaderRegistry

GraphQL Java 中完整的批处理加载机制需要实现几个BatchLoader接口中的一个,然后用DataLoader中的名称将这些接口包装并注册为DataLoaders。

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 参数,而不需要指定名称。但是,如果有必要,可以通过BatchLoaderRegistryBuilder 自定义名称,以及其他DataLoader选项。

对于许多情况,在加载相关实体时,可以使用@batchmapping控制器方法,这对于需要使用BatchLoaderRegistryDataLoader直接替换的快捷方式也提供了重要的好处。SBatchLoaderRegistry也提供了其他好处。它支持从批装载函数和从@BatchMapping方法访问相同的GraphQLContext,并确保上下文传播到它们。这就是为什么应用程序需要使用它。可以直接执行你自己的DataLoader注册,但这种注册将放弃上述好处。

# 4.5.3. 测试批装载

首先让BatchLoaderRegistryDataLoaderRegistry上执行注册:

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”。要对此进行定制,可以使用QuerydslDataFetcherBuilder 方法来提供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 类型的名称必须与存储库域类型的简单名称匹配。如果需要,可以使用@GraphQlRepositorytypeName属性来指定目标 GraphQL 类型名。

自动注册检测给定存储库是否实现QuerydslBinderCustomizer,并通过QuerydslDataFetcherBuilder 方法透明地应用该方法。

自动注册是通过可从QuerydslDataFetcher获得的内置RuntimeWiringConfigurer执行的。引导启动器 (opens new window)自动检测@GraphQlRepositorybean,并使用它们初始化RuntimeWiringConfigurerwith。

自动注册不支持定制。如果需要,你需要使用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 类型的名称必须与存储库域类型的简单名称匹配。如果需要,可以使用@GraphQlRepositorytypeName属性来指定目标 GraphQL 类型名。

自动注册是通过可从QueryByExampleDataFetcher获得的内置RuntimeWiringConfigurer执行的。引导启动器 (opens new window)自动检测@GraphQlRepositorybean,并使用它们初始化RuntimeWiringConfigurerwith。

自动注册不支持定制。如果需要,你需要使用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。有两种类型的接口预测,它们都决定从底层数据源加载哪些属性:

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. 声明

你可以将@Controllerbean 定义为标准 Spring Bean 定义。该@Controller原型允许自动检测,与 Spring 对齐,用于在 Classpath 上检测@Controller@Component类并为它们自动注册 Bean 定义。它还充当带注释的类的原型,指示其作为 GraphQL 应用程序中的数据获取组件的角色。

AnnotatedControllerConfigurer通过RuntimeWiring.Builder检测@Controllerbean 并将其注释的处理程序方法注册为DataFetchers。它是RuntimeWiringConfigurer的一个实现,它可以被添加到GraphQlSource.Builder中。 Spring 引导启动器会自动将AnnotatedControllerConfigurer声明为 Bean,并将所有RuntimeWiringConfigurerbean 添加到GraphQlSource.Builder中,从而支持带注释的DataFetchers,请参见引导启动器文档中的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进行注释,并且将类型名称预设为QueryMutationSubscription。实际上,这些是分别针对查询、突变和订阅类型下的字段的快捷方式注释。例如:

@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
的实例,或者来自GraphQLContextDataFetchingEnvironment的实例。
GraphQLContext 用于从DataFetchingEnvironment访问上下文。
java.security.Principal 从 Spring 安全上下文中获得的,如果可用的话。
@AuthenticationPrincipal 用于从 Spring 安全上下文访问Authentication#getPrincipal()
DataFetchingFieldSelectionSet 用于通过DataFetchingEnvironment访问查询的选择集。
Locale, Optional<Locale> DataFetchingEnvironment访问Locale
DataFetchingEnvironment 直接访问底层DataFetchingEnvironment

模式映射处理程序方法可以返回任意值,包括反应器MonoFlux中描述的[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也透明地绑定到类型authorauthor字段,对于作者来说,它只是将其委托给DataLoader,给定其源/父Book实例。

要作为唯一键使用,Book必须实现hashcodeequals

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

# 6.3.1. 方法签名

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

Method Argument 说明
List<K> 源/父对象。
java.security.Principal 从 Spring 安全上下文获得(如果可用的话)。
@ContextValue 要访问来自GraphQLContextBatchLoaderEnvironment的值,请使用
,该上下文与来自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.graphqlinsrc/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