|
此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring GraphQL 1.4.0! |
带注释的控制器
Spring for GraphQL 提供了一个基于注释的编程模型,其中@Controller组件使用注解来声明具有灵活方法签名的处理程序方法,以
获取特定 GraphQL 字段的数据。例如:
@Controller
public class GreetingController {
@QueryMapping (1)
public String hello() { (2)
return "Hello, world!";
}
}
| 1 | 将此方法绑定到查询,即 Query 类型下的字段。 |
| 2 | 如果未在注释上声明,则从方法名称确定查询。 |
Spring for GraphQL 使用RuntimeWiring.Builder要将上述处理程序方法注册为graphql.schema.DataFetcher对于名为 “hello” 的查询。
声明
您可以定义@Controllerbeans 作为标准的 Spring bean 定义。这@Controllerstereotype 允许自动检测,与 Spring 通用一致
支持检测@Controller和@Component类路径和
为它们自动注册 bean 定义。它还充当带注释的
类,指示其在 GraphQL 应用程序中作为数据获取组件的角色。
AnnotatedControllerConfigurer检测@Controllerbean 并注册其
注解的处理程序方法为DataFetcherS 通过RuntimeWiring.Builder.它是一个
实现RuntimeWiringConfigurer可以添加到GraphQlSource.Builder.
Boot Starter 会自动声明AnnotatedControllerConfigurer作为 Bean
并添加所有RuntimeWiringConfigurerbeans 设置为GraphQlSource.Builder这使得
支持 AnnotatedDataFetcher,请参阅 GraphQL RuntimeWiring 部分
在 Boot Starter 文档中。
@SchemaMapping
这@SchemaMappingannotation 将处理程序方法映射到 GraphQL 架构中的字段
并声明它是DataFetcher对于该字段。注解可以指定
父类型名称,以及字段名称:
@Controller
public class BookController {
@SchemaMapping(typeName="Book", field="author")
public Author getAuthor(Book book) {
// ...
}
}
这@SchemaMappingannotation 也可以省略这些属性,在这种情况下,
Field Name 默认为 Method Name,而 Type Name 默认为 Simple 类
注入方法的源/父对象的名称。例如,下面的
默认为 type “Book” 和字段 “author”:
@Controller
public class BookController {
@SchemaMapping
public Author author(Book book) {
// ...
}
}
这@SchemaMapping注解可以在类级别声明以指定默认的
键入类中所有处理程序方法的 name。
@Controller
@SchemaMapping(typeName="Book")
public class BookController {
// @SchemaMapping methods for fields of the "Book" type
}
@QueryMapping,@MutationMapping和@SubscriptionMapping是元注释,这些
本身被注释为@SchemaMapping并将 typeName 预设为Query,Mutation或Subscription分别。实际上,这些是快捷方式注释
for 字段 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处理程序方法具有灵活的签名,并且可以从一系列
方法参数和返回值..
方法参数
架构映射处理程序方法可以具有以下任何方法参数:
| 方法参数 | 描述 |
|---|---|
|
用于访问绑定到更高级别类型化 Object 的命名字段参数。 |
|
用于访问原始参数值。 |
|
用于访问绑定到更高级别的类型化 Object 的命名字段参数
带有一个标志,用于指示是否省略了 input 参数,而不是设置为 |
|
用于访问绑定到更高级别类型化 Object 的所有字段参数。 |
|
用于访问参数的原始映射。 |
|
用于通过项目接口访问字段参数。 |
“来源” |
用于访问字段的源(即父级/容器)实例。 请参阅源代码。 |
|
用于访问分页参数。 参见 Pagination, Scroll, |
|
用于访问排序详细信息。 参见 Pagination, |
|
要访问 |
|
要从主 |
|
要从本地 |
|
要从 |
|
从 Spring Security 上下文获取(如果可用)。 |
|
要访问 |
|
要通过 |
|
要访问 |
|
用于直接访问底层 |
返回值
架构映射处理程序方法可以返回:
-
任何类型的 resolved 值。
-
Mono和Flux对于异步值。支持控制器方法和 任何DataFetcher如反应性的DataFetcher. -
Kotlin 协程和
Flow适应于Mono和Flux. -
java.util.concurrent.Callable以异步方式生成值。 要使其正常工作,AnnotatedControllerConfigurer必须配置Executor.
在 Java 21+ 上,当AnnotatedControllerConfigurer配置了Executor控制器
具有阻塞方法签名的方法将异步调用。默认情况下,控制器
如果 method 不返回异步类型(如Flux,Mono,CompletableFuture,也不是 Kotlin 挂起函数。您可以配置
阻塞控制器方法Predicate上AnnotatedControllerConfigurer帮助
确定哪些方法被视为阻塞。
Spring Boot starter for Spring for GraphQL 会自动配置AnnotatedControllerConfigurer替换为Executor对于虚拟线程,当属性spring.threads.virtual.enabled已设置。 |
接口架构映射
当控制器方法映射到架构接口字段时,默认情况下,映射为 替换为多个映射,每个映射对应实现接口的每个架构对象类型。 这允许对所有子类型使用一个控制器方法。
例如,给定:
type Query {
activities: [Activity!]!
}
interface Activity {
id: ID!
coordinator: User!
}
type FooActivity implements Activity {
id: ID!
coordinator: User!
}
type BarActivity implements Activity {
id: ID!
coordinator: User!
}
type User {
name: String!
}
您可以编写这样的控制器:
@Controller
public class BookController {
@QueryMapping
public List<Activity> activities() {
// ...
}
@SchemaMapping
public User coordinator(Activity activity) {
// Called for any Activity subtype
}
}
如有必要,您可以接管各个子类型的映射:
@Controller
public class BookController {
@QueryMapping
public List<Activity> activities() {
// ...
}
@SchemaMapping
public User coordinator(Activity activity) {
// Called for any Activity subtype except FooActivity
}
@SchemaMapping
public User coordinator(FooActivity activity) {
// ...
}
}
@Argument
在 GraphQL Java 中,DataFetchingEnvironment提供对特定于字段的映射的访问权限
argument 值。这些值可以是简单的标量值(例如 String、Long)、一个Map之
值进行更复杂的输入,或者List的值。
使用@Argumentannotation 将参数绑定到目标对象,并将
注入到 handler 方法中。通过将参数值映射到
primary data 构造函数,或者使用默认的
constructor 创建对象,然后将 argument 值映射到其属性。这是
递归重复,使用所有嵌套参数值并创建嵌套目标对象
因此。例如:
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}
@MutationMapping
public Book addBook(@Argument BookInput bookInput) {
// ...
}
}
如果目标对象没有 setter,并且您无法更改它,则可以使用
属性AnnotatedControllerConfigurer允许通过 Direct 进行回退绑定
字段访问。 |
默认情况下,如果方法参数名称可用(需要-parameters编译器
标志替换为 Java 8+ 或编译器中的调试信息),它用于查找参数。
如果需要,您可以通过 annotation 自定义名称,例如@Argument("bookInput").
这@Argumentannotation 没有 “required” 标志,也没有
指定 Default (默认值)。这两者都可以在 GraphQL 架构级别指定,并且
由 GraphQL Java 强制执行。 |
如果绑定失败,则BindException引发时,绑定问题累积为字段
错误,其中field是出现问题的参数路径。
您可以使用@Argument替换为Map<String, Object>参数,以获取
参数。例如:
@Controller
public class BookController {
@MutationMapping
public Book addBook(@Argument Map<String, Object> bookInput) {
// ...
}
}
在 1.2 之前,@Argument Map<String, Object>如果
注释未指定名称。在 1.2 之后,@Argument跟Map<String, Object>始终返回 Raw 参数值,并与名称匹配
在注释中指定,或添加到参数名称。要访问完整参数
map 中,请使用@Arguments相反。 |
ArgumentValue
默认情况下,GraphQL 中的输入参数是可为 null 的可选参数,即参数
可以设置为nullliteral 的 Literal 或根本不提供。这种区别对于
部分更新,其中底层数据也可能是null或者根本没有相应地改变。使用@Argument没有办法做出这样的区分,因为你会得到null或空的Optional在这两种情况下。
如果你想知道某个值是否根本没有提供,你可以声明一个ArgumentValuemethod 参数,该参数是结果值的简单容器
以及一个标志,以指示是否完全省略了 input 参数。你
可以使用 this 而不是@Argument,在这种情况下,参数名称由
方法参数名称,或者与@Argument以指定参数名称。
例如:
@Controller
public class BookController {
@MutationMapping
public void addBook(ArgumentValue<BookInput> bookInput) {
if (!bookInput.isOmitted()) {
BookInput value = bookInput.value();
// ...
}
}
}
ArgumentValue也支持作为@Argumentmethod 参数,可以通过构造函数参数或通过 setter 初始化,包括
作为嵌套在顶级对象下任何级别的对象的字段。
@Arguments
使用@Arguments注解,如果要将完整的 arguments 映射到单个
target Object 与@Argument,它绑定一个特定的命名参数。
例如@Argument BookInput bookInput使用参数 “bookInput” 的值
初始化BookInput而@Arguments使用完整的参数 map,并在该
case 时,顶级参数绑定到BookInput性能。
您可以使用@Arguments替换为Map<String, Object>参数获取
all 参数值。
@ProjectedPayload接口
作为使用 完整 Object 的替代方法@Argument,
您还可以使用投影接口通过
定义明确的最小接口。当 Spring Data 位于 class path 上时,参数投影由 Spring Data 的接口投影提供。
要使用此功能,请创建一个带有@ProjectedPayload并声明
it 作为控制器方法参数。如果参数带有@Argument,
它适用于DataFetchingEnvironment.getArguments()地图。当声明时没有@Argument,则投影适用于
完整的 arguments 映射。
例如:
@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();
}
源
在 GraphQL Java 中,DataFetchingEnvironment提供对源(即
parent/container) 实例。要访问它,只需声明一个 method 参数
的预期目标类型。
@Controller
public class BookController {
@SchemaMapping
public Author author(Book book) {
// ...
}
}
source method 参数还有助于确定 Map 的类型名称。
如果 Java 类的简单名称与 GraphQL 类型匹配,则无需
在@SchemaMapping注解。
|
一个 |
Subrange
当存在CursorStrategySpring 配置中的 bean,
控制器方法支持Subrange<P>参数,其中<P>是相对位置
从游标转换而来。对于 Spring Data,ScrollSubrange公开ScrollPosition.
例如:
@Controller
public class BookController {
@QueryMapping
public Window<Book> books(ScrollSubrange subrange) {
ScrollPosition position = subrange.position().orElse(ScrollPosition.offset());
int count = subrange.count().orElse(20);
// ...
}
}
有关分页和内置机制的概述,请参阅 Pagination。
Sort
当 Spring 配置中有 SortStrategy bean 时,控制器
methods 支持Sort作为方法参数。例如:
@Controller
public class BookController {
@QueryMapping
public Window<Book> books(Optional<Sort> optionalSort) {
Sort sort = optionalSort.orElse(Sort.by(..));
}
}
DataLoader
当您为实体注册批量加载函数时,如批量加载中所述,您可以访问DataLoader通过声明
method 参数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) 作为注册的密钥,因此只需声明
这DataLoadermethod 参数提供足够的信息
要在DataLoaderRegistry.作为回退,DataLoadermethod 参数
Resolver 还会尝试将方法参数 name 作为键,但通常不应这样做
是必要的。
请注意,对于加载相关实体的许多情况,其中@SchemaMapping只是
delegates 传递给DataLoader,您可以使用@BatchMapping方法减少样板,如下一节所述。
验证
当javax.validation.Validatorbean 的AnnotatedControllerConfigurer启用对带 Comments 的 Controller 方法的 Bean 验证的支持。通常,该 bean 的类型为LocalValidatorFactoryBean.
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被提升。
您可以使用 Exceptions 链来决定如何将其呈现给客户端
将其转换为错误以包含在 GraphQL 响应中。
除了@Valid,您还可以使用 Spring 的@Validated这允许
指定验证组。 |
Bean 验证可用于@Argument,@Arguments@ProjectedPayload方法参数,但更普遍地适用于任何方法参数。
|
验证和 Kotlin 协程
Hibernate Validator 与 Kotlin 协程方法不兼容,并且在 内省他们的方法参数。请参阅 spring-projects/spring-graphql#344 (评论) 以获取相关问题的链接和建议的解决方法。 |
@BatchMapping
批量加载通过使用org.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());
}
}
对于加载关联实体的直接情况(如上所示),@SchemaMapping方法只不过是将DataLoader.这是
样板,可以使用@BatchMapping方法。例如:
@Controller
public class BookController {
@BatchMapping
public Mono<Map<Book, Author>> author(List<Book> books) {
// ...
}
}
以上就变成了BatchLoaderRegistry键所在的位置Book实例和 loaded 的值的作者。此外,一个DataFetcher也透明地绑定到author类型为Book哪
只需将DataLoader对于作者,给定其源/父级Book实例。
|
要用作唯一键, |
默认情况下,字段名称默认为方法名称,而类型名称默认为
输入的简单类名List元素类型。两者都可以通过以下方式进行定制
annotation 属性。类型名称也可以从类级别继承@SchemaMapping.
方法参数
批量映射方法支持以下参数:
| 方法参数 | 描述 |
|---|---|
|
源/父对象。 |
|
从 Spring Security 上下文获取(如果可用)。 |
|
要从 |
|
要从 |
|
GraphQL Java 中可用的环境到 这 这 |
返回值
批量映射方法可以返回:
| 返回类型 | 描述 |
|---|---|
|
一个映射,其中父对象作为键,批量加载的对象作为值。 |
|
批量加载的对象序列,必须与源/父对象的顺序相同 对象。 |
|
命令式变体,例如,无需远程调用。 |
|
异步调用的命令式变体。要使其正常工作, |
Kotlin 协程与 |
适应于 |
在 Java 21+ 上,当AnnotatedControllerConfigurer配置了Executor控制器
具有阻塞方法签名的方法将异步调用。默认情况下,控制器
如果 method 不返回异步类型(如Flux,Mono,CompletableFuture,也不是 Kotlin 挂起函数。您可以配置
阻塞控制器方法Predicate上AnnotatedControllerConfigurer帮助
确定哪些方法被视为阻塞。
Spring Boot starter for Spring for GraphQL 会自动配置AnnotatedControllerConfigurer替换为Executor对于虚拟线程,当属性spring.threads.virtual.enabled已设置。 |
接口批处理映射
与 Interface Schema Mappings 一样, 将批处理映射方法映射到 Schema Interface 字段时,该映射将替换为 多个映射,每个映射用于实现接口的每个 Schema 对象类型。
这意味着,给定以下内容:
type Query {
activities: [Activity!]!
}
interface Activity {
id: ID!
coordinator: User!
}
type FooActivity implements Activity {
id: ID!
coordinator: User!
}
type BarActivity implements Activity {
id: ID!
coordinator: User!
}
type User {
name: String!
}
您可以编写这样的控制器:
@Controller
public class BookController {
@QueryMapping
public List<Activity> activities() {
// ...
}
@BatchMapping
Map<Activity, User> coordinator(List<Activity> activities) {
// Called for all Activity subtypes
}
}
如有必要,您可以接管各个子类型的映射:
@Controller
public class BookController {
@QueryMapping
public List<Activity> activities() {
// ...
}
@BatchMapping
Map<Activity, User> coordinator(List<Activity> activities) {
// Called for all Activity subtypes
}
@BatchMapping(field = "coordinator")
Map<Activity, User> fooCoordinator(List<FooActivity> activities) {
// ...
}
}
@GraphQlExceptionHandler
用@GraphQlExceptionHandler处理数据获取异常的方法
灵活的方法签名。当在
controller 时,异常处理程序方法适用于来自同一控制器的异常:
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.GraphQlExceptionHandler;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument Long id) {
return ...
}
@GraphQlExceptionHandler
public GraphQLError handle(GraphqlErrorBuilder<?> errorBuilder, BindException ex) {
return errorBuilder
.errorType(ErrorType.BAD_REQUEST)
.message(ex.getMessage())
.build();
}
}
当在@ControllerAdvice,异常处理程序方法跨控制器应用:
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import org.springframework.graphql.data.method.annotation.GraphQlExceptionHandler;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ControllerAdvice
public class GlobalExceptionHandler {
@GraphQlExceptionHandler
public GraphQLError handle(GraphqlErrorBuilder<?> errorBuilder, BindException ex) {
return errorBuilder
.errorType(ErrorType.BAD_REQUEST)
.message(ex.getMessage())
.build();
}
}
如上面的示例所示,您应该通过注入GraphQlErrorBuilder在方法签名中
因为它已经准备好了当前的DataFetchingEnvironment.
异常处理方式@GraphQlExceptionHandler方法会自动应用于
控制器调用。处理来自其他graphql.schema.DataFetcher实现,而不是基于 Controller 方法,获取DataFetcherExceptionResolver从AnnotatedControllerConfigurer,并在GraphQlSource.Builder作为 DataFetcherExceptionResolver 进行分配。
方法签名
异常处理程序方法支持带有方法参数的灵活方法签名
从DataFetchingEnvironment,并与 @SchemaMapping 方法的 MATCH 匹配。
支持的返回类型如下:
| 返回类型 | 描述 |
|---|---|
|
将异常解决为单字段错误。 |
|
解决多个字段错误的异常。 |
|
解决异常而不出现响应错误。 |
|
将异常解决为单个错误、多个错误或无异常。
返回值必须为 |
|
对于异步解析,其中 |
命名空间
在 schema 级别,query 和 mutation作直接在Query和Mutation类型。
丰富的 GraphQL API 可以定义数十个作来区分这些类型,这使得探索 API 和分离关注点变得更加困难。
您可以选择在 GraphQL 架构中定义命名空间。
虽然这种方法有一些注意事项,但您可以使用 Spring for GraphQL 注释的控制器实现此模式。
例如,使用命名空间,您的 GraphQL 架构可以将查询作嵌套在顶级类型下,而不是直接将它们列在Query.
在这里,我们将定义MusicQueries和UserQueries类型,并使其在Query:
type Query {
music: MusicQueries
users: UserQueries
}
type MusicQueries {
album(id: ID!): Album
searchForArtist(name: String!): [Artist]
}
type Album {
id: ID!
title: String!
}
type Artist {
id: ID!
name: String!
}
type UserQueries {
user(login: String): User
}
type User {
id: ID!
login: String!
}
GraphQL 客户端将使用album查询,如下所示:
{
music {
album(id: 42) {
id
title
}
}
}
并得到以下响应:
{
"data": {
"music": {
"album": {
"id": "42",
"title": "Spring for GraphQL"
}
}
}
}
这可以在@Controller使用以下模式:
import java.util.List;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
@Controller
@SchemaMapping(typeName = "MusicQueries") (1)
public class MusicController {
@QueryMapping (2)
public MusicQueries music() {
return new MusicQueries();
}
(3)
public record MusicQueries() {
}
@SchemaMapping (4)
public Album album(@Argument String id) {
return new Album(id, "Spring GraphQL");
}
@SchemaMapping
public List<Artist> searchForArtist(@Argument String name) {
return List.of(new Artist("100", "the Spring team"));
}
}
| 1 | 使用@SchemaMapping以及typeName属性,以避免在方法上重复它 |
| 2 | 定义一个@QueryMapping对于 “music” 命名空间 |
| 3 | “music” 查询返回 “空” 记录,但也可能返回空地图 |
| 4 | 查询现在声明为 “MusicQueries” 类型下的字段 |
无需在控制器中显式声明包装类型(“MusicQueries”、“UserQueries”),不如在控制器中显式声明包装类型
您可以选择使用GraphQlSourceBuilderCustomizer使用 Spring Boot:
import java.util.Collections;
import java.util.List;
import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class NamespaceConfiguration {
@Bean
public GraphQlSourceBuilderCustomizer customizer() {
List<String> queryWrappers = List.of("music", "users"); (1)
return (sourceBuilder) -> sourceBuilder.configureRuntimeWiring((wiringBuilder) ->
queryWrappers.forEach((field) -> wiringBuilder.type("Query",
(builder) -> builder.dataFetcher(field, (env) -> Collections.emptyMap()))) (2)
);
}
}
| 1 | 列出 “Query” 类型的所有包装类型 |
| 2 | 为每个 Map 手动声明数据获取器,返回一个空的 Map |