Annotated Controllers
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
将上述处理程序方法注册为名为“hello”的查询的 graphql.schema.DataFetcher
。
Declaration
你可以将 @Controller
bean 定义为标准 Spring bean 定义。@Controller
刻板印象允许自动检测,这与 Spring 对检测类路径上的 @Controller
和 @Component
类以及自动注册它们的 bean 定义的一般支持一致。它还充当注解类的刻板印象,指示其在 GraphQL 应用程序中作为数据获取组件的角色。
AnnotatedControllerConfigurer
检测 @Controller
bean,并将其带注释的处理程序方法注册为 DataFetcher`s via `RuntimeWiring.Builder
。它是 RuntimeWiringConfigurer
的实现,可以添加到 GraphQlSource.Builder
。 Boot Starter 自动声明 AnnotatedControllerConfigurer
为 bean,并将所有 RuntimeWiringConfigurer
bean 添加到 GraphQlSource.Builder
,这样便支持带注释的 DataFetcher
,请参阅启动器文档中的 GraphQL RuntimeWiring 部分。
@SchemaMapping
@SchemaMapping
注解将一个处理程序方法映射到 GraphQL schema 中的一个字段,并将其声明为该字段的 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
注解,并且将 typeName 预设为 Query
、Mutation
或 Subscription
。实际上,这些是对 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
处理程序方法具有灵活的签名,并且可以在一系列方法参数和返回值中进行选择。
Method Signature
Schema 映射处理程序方法可以具有以下任何方法参数:
Method Argument | Description |
---|---|
|
可访问绑定到更高级别、类型化对象的命名字段参数。请参阅 |
|
可访问原始参数值。请参阅 |
|
可访问绑定到更高级别、类型化对象的命名字段参数,以及一个标志,用于指示输入参数是省略了还是设置为 |
|
可访问绑定到更高级别、类型化对象的全部字段参数。请参阅 |
|
要访问原始参数映射。 |
|
可通过项目接口访问字段参数。请参阅 |
"Source" |
可访问字段的源(即父级/容器)实例。请参阅 Source。 |
|
可访问分页参数。请参阅 Pagination、Scroll、 |
|
可访问排序详细信息。请参阅 Pagination、 |
|
可访问 |
|
对于从 |
|
对于从 |
|
对于从 |
|
从 Spring Security 上下文中获取,如果可用。 |
|
对于从 Spring Security 上下文中访问 |
|
对于通过 |
|
对于从 |
|
对于直接访问底层 |
Schema 映射处理程序方法可以返回:
-
任何类型的解析值。
-
Mono`和 `Flux`用于异步值。支持控制器方法,以及任何 `DataFetcher
,如 ReactiveDataFetcher
中所述。 -
`java.util.concurrent.Callable`让值异步生成。要做到这一点,`AnnotatedControllerConfigurer`必须使用 `Executor`进行配置。
@Argument
在 GraphQL Java 中,DataFetchingEnvironment
提供对特定于字段的参数值的映射的访问。这些值可以是简单的标量值(例如 String、Long)、用于更复杂的输入的 Map
或值的 List
。
使用 @Argument
注解使一个参数绑定到目标对象并注入到处理程序方法中。绑定是通过将参数值映射到预期的使用方法参数类型的基本数据构造函数或使用默认构造函数创建对象然后将参数值映射到其属性来执行的。这会递归重复,使用所有嵌套参数值,并相应地创建嵌套目标对象。例如:
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}
@MutationMapping
public Book addBook(@Argument BookInput bookInput) {
// ...
}
}
如果目标对象没有 setter,并且您无法更改它,则可以使用 |
如果没有给出 method 参数名(Java 8 以上版本中需要使用 -parameters
编译器标记,或者要求编译器的调试信息),那么将使用它来查找参数。如果需要,您可以通过注释来自定义名称,例如:@Argument("bookInput")
.
|
如果绑定失败,则会抛出一个 BindException
,它会将绑定问题积累为 field 错误,其中每个错误的 field
都是出现问题后的参数路径。
使用一个 Map<String, Object>
参数,并配以 @Argument
,就能获取参数的原始值。例如:
@Controller
public class BookController {
@MutationMapping
public Book addBook(@Argument Map<String, Object> bookInput) {
// ...
}
}
在 1.2 之前,如果注释未指定名称, |
ArgumentValue
默认情况下,GraphQL 中的输入参数是可空的和可选的,这意味着可以在 null
文字中设置参数,或者根本不提供参数。这种区分对部分更新很有用,其中底层数据也可以相应地设置成 null
或根本不更改。当使用 xref:controllers.adoc#controllers.schema-mapping.argument[@Argument
时,无法做出这种区分,因为在两种情况下你都会得到 null
或一个空 Optional
。
如果您想了解一个值是否根本没有提供,则可以声明一个 ArgumentValue
方法参数,它是一个结果值的一个简单容器,并带有标志,以表示输入参数是否全部都已省略。您可以使用它来代替 @Argument
,在这种情况下,参数名将根据方法参数名来决定,或者与 @Argument
一起使用来指定参数名。
例如:
@Controller
public class BookController {
@MutationMapping
public void addBook(ArgumentValue<BookInput> bookInput) {
if (!bookInput.isOmitted()) {
BookInput value = bookInput.value();
// ...
}
}
}
ArgumentValue
也被支持为一个 @Argument
方法参数的对象结构中的一个字段,可以经由一个构造器参数或一个 setter 来初始化,包括作为位于顶层对象下任何级别的对象的字段。
@Arguments
如果您想将完整的参数映射捆绑到一个单独的目标对象上,而不是捆绑一个特定的带名字的参数,那么请使用 @Arguments
注释。
例如,@Argument BookInput bookInput
使用参数 "bookInput" 的值来初始化 BookInput
,而 @Arguments
使用完整的参数映射,在这种情况下,顶层参数将被捆绑到 BookInput
属性。
使用一个 Map<String, Object>
参数,并配以 @Arguments
,就能获取所有参数值的原始映射。
@ProjectedPayload
Interface
作为使用具有 @Argument
的完整对象的备选方案,您还可以使用投影接口通过明确定义的最小接口访问 GraphQL 请求参数。当 Spring Data 位于类路径中时,参数投影由 Spring Data’s Interface projections 提供。
要使用此方法,请创建一个带有 @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();
}
Source
在 GraphQL Java 中,DataFetchingEnvironment
提供对字段的来源(即父级/容器)实例的访问权限。要访问此方法,只需声明一个带有目标类型的预期方法参数。
@Controller
public class BookController {
@SchemaMapping
public Author author(Book book) {
// ...
}
}
源方法参数也有助于确定映射的类型名称。如果 Java 类的简单名称与 GraphQL 类型匹配,那么不需要在 @SchemaMapping
注释中显式指定类型名称。
|
Subrange
当 Spring 配置中有一个 xref:request-execution.adoc#execution.pagination.cursor.strategy[CursorStrategy
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 时,控制器方法支持将 Sort
作为方法参数。例如:
@Controller
public class BookController {
@QueryMapping
public Window<Book> books(Optional<Sort> optionalSort) {
Sort sort = optionalSort.orElse(Sort.by(..));
}
}
DataLoader
当为实体注册批量加载函数(如 Batch Loading 中所述)时,可以通过声明 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
的类名)的完整类名作为注册的密钥,因此,只需用泛型类型声明 DataLoader
方法参数就能提供足够的信息在 DataLoaderRegistry
中找到它。作为备选方案,DataLoader
方法参数解析器还会尝试使用方法参数名作为密钥,但一般来说,这是没有必要的。
请注意,对于许多与加载相关实体的情况,其中 @SchemaMapping
仅仅委派给 DataLoader
,你可以通过使用下一节中所述的 @BatchMapping
方法来减少样板代码。
Validation
当找到 javax.validation.Validator
bean 时, AnnotatedControllerConfigurer
允许在带注释的控制器方法上使用 Bean Validation。通常,bean 的类型为 LocalValidatorFactoryBean
。
Bean Validation 让你可以声明类型约束:
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 响应中的错误。
除 |
Bean 验证适用于 @Argument`
、`
@Arguments`` 和 @ProjectedPayload
方法参数,但更普遍地适用于任何方法参数。
Hibernate Validator 与 Kotlin 协程方法不兼容,在内省其方法参数时会失败。请参阅 spring-projects/spring-graphql#344 (comment) 以获取相关问题的链接和建议的解决方法。
@BatchMapping
Batch Loading
通过使用 org.dataloader.DataLoader
来延迟加载各个实体实例,以便可以将它们一起加载,从而解决了 N+1 选择问题。例如:
@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` 透明地绑定到 Book
类型的 author
字段,它只是给定其源/父 Book
实例,委派给作者的`DataLoader`。
为了用作唯一键, |
默认情况下,字段名称默认为方法名称,而类型名称默认为输入 List
元素类型的简单类名称。两者都可以通过注释属性自定义。类型名称也可以从类级别的`@SchemaMapping` 继承。
Method Signature
批处理映射方法支持以下参数:
Method Argument | Description |
---|---|
|
The source/parent objects. |
|
从 Spring Security 环境中获取(如果存在)。 |
|
要访问 |
|
要访问 |
|
在 GraphQL Java 中可用的 |
批处理映射方法可以返回:
Return Type | Description |
---|---|
|
一个映射,其中父对象为键,批量加载的对象为值。 |
|
按相同顺序加载对象的序列,与传入该方法的源/父对象保持一致。 |
|
命令变型,例如,无需进行远程调用。 |
|
要异步调用的必要变体。要实现此功能, |
@GraphQlExceptionHandler
使用 @GraphQlExceptionHandler
方法来使用灵活的 method signature
处理数据获取异常。当在控制器中声明时,异常处理程序方法适用于来自同一控制器的异常:
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}
@GraphQlExceptionHandler
public GraphQLError handle(BindException ex) {
return GraphQLError.newError().errorType(ErrorType.BAD_REQUEST).message("...").build();
}
}
在 @ControllerAdvice
中声明时,异常处理程序方法应用于所有控制器:
@ControllerAdvice
public class GlobalExceptionHandler {
@GraphQlExceptionHandler
public GraphQLError handle(BindException ex) {
return GraphQLError.newError().errorType(ErrorType.BAD_REQUEST).message("...").build();
}
}
通过 @GraphQlExceptionHandler
方法进行异常处理会自动应用于控制器调用。要处理来自其他 graphql.schema.DataFetcher
实现的异常(不基于控制器方法),请从 AnnotatedControllerConfigurer
获取 DataFetcherExceptionResolver
,并将其作为 DataFetcherExceptionResolver
注册到 GraphQlSource.Builder
中。
Method Signature
异常处理方法支持灵活的方法签名,其中方法参数从 DataFetchingEnvironment,
中解析,并与 @SchemaMapping methods
的方法参数匹配。
支持的返回类型如下列出:
Return Type | Description |
---|---|
|
将异常解析为单个字段错误。 |
|
将异常解析为多个字段错误。 |
|
解析异常,没有响应错误。 |
|
将异常解析为单个错误、多个错误或无错误。 返回值必须是 |
|
用于 |