Annotated Controllers
Spring for GraphQL 提供了一个基于注解的编程模型,其中 @Controller
组件使用注解来声明具有灵活方法签名的处理程序方法,以获取特定 GraphQL 字段的数据。例如:
Spring for GraphQL provides an annotation-based programming model where @Controller
components use annotations to declare handler methods with flexible method signatures to
fetch the data for specific GraphQL fields. For example:
@Controller
public class GreetingController {
@QueryMapping (1)
public String hello() { (2)
return "Hello, world!";
}
}
1 | Bind this method to a query, i.e. a field under the Query type. |
2 | Determine the query from the method name if not declared on the annotation. |
Spring for GraphQL 使用 RuntimeWiring.Builder
将上述处理程序方法注册为名为“hello”的查询的 graphql.schema.DataFetcher
。
Spring for GraphQL uses RuntimeWiring.Builder
to register the above handler method as a
graphql.schema.DataFetcher
for the query named "hello".
Declaration
你可以将 @Controller
bean 定义为标准 Spring bean 定义。@Controller
刻板印象允许自动检测,这与 Spring 对检测类路径上的 @Controller
和 @Component
类以及自动注册它们的 bean 定义的一般支持一致。它还充当注解类的刻板印象,指示其在 GraphQL 应用程序中作为数据获取组件的角色。
You can define @Controller
beans as standard Spring bean definitions. The
@Controller
stereotype allows for auto-detection, aligned with Spring general
support for detecting @Controller
and @Component
classes on the classpath and
auto-registering bean definitions for them. It also acts as a stereotype for the annotated
class, indicating its role as a data fetching component in a GraphQL application.
AnnotatedControllerConfigurer
检测 @Controller
bean,并将其带注释的处理程序方法注册为 DataFetcher`s via `RuntimeWiring.Builder
。它是 RuntimeWiringConfigurer
的实现,可以添加到 GraphQlSource.Builder
。 Boot Starter 自动声明 AnnotatedControllerConfigurer
为 bean,并将所有 RuntimeWiringConfigurer
bean 添加到 GraphQlSource.Builder
,这样便支持带注释的 DataFetcher
,请参阅启动器文档中的 GraphQL RuntimeWiring 部分。
AnnotatedControllerConfigurer
detects @Controller
beans and registers their
annotated handler methods as DataFetcher`s via `RuntimeWiring.Builder
. It is an
implementation of RuntimeWiringConfigurer
which can be added to GraphQlSource.Builder
.
The Boot Starter automatically declares AnnotatedControllerConfigurer
as a bean
and adds all RuntimeWiringConfigurer
beans to GraphQlSource.Builder
and that enables
support for annotated `DataFetcher`s, see the
GraphQL RuntimeWiring section
in the Boot starter documentation.
@SchemaMapping
@SchemaMapping
注解将一个处理程序方法映射到 GraphQL schema 中的一个字段,并将其声明为该字段的 DataFetcher
。注解可以指定父类型名称和字段名称:
The @SchemaMapping
annotation maps a handler method to a field in the GraphQL schema
and declares it to be the DataFetcher
for that field. The annotation can specify the
parent type name, and the field name:
@Controller
public class BookController {
@SchemaMapping(typeName="Book", field="author")
public Author getAuthor(Book book) {
// ...
}
}
@SchemaMapping
注解也可以省去那些属性,在这种情况下,字段名称默认为方法名,而类型名称默认为注射到方法中的源/父对象的简单类名。例如,下面默认为类型“Book”和字段“author”:
The @SchemaMapping
annotation can also leave out those attributes, in which case the
field name defaults to the method name, while the type name defaults to the simple class
name of the source/parent object injected into the method. For example, the below
defaults to type "Book" and field "author":
@Controller
public class BookController {
@SchemaMapping
public Author author(Book book) {
// ...
}
}
@SchemaMapping
注解可以在类级别声明,为类中的所有处理程序方法指定一个默认类型名称。
The @SchemaMapping
annotation can be declared at the class level to specify a default
type name for all handler methods in the class.
@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 类型下的字段的快捷方式注解。例如:
@QueryMapping
, @MutationMapping
, and @SubscriptionMapping
are meta annotations that
are themselves annotated with @SchemaMapping
and have the typeName preset to Query
,
Mutation
, or Subscription
respectively. Effectively, these are shortcut annotations
for fields under the Query, Mutation, and Subscription types respectively. For example:
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}
@MutationMapping
public Book addBook(@Argument BookInput bookInput) {
// ...
}
@SubscriptionMapping
public Flux<Book> newPublications() {
// ...
}
}
@SchemaMapping
处理程序方法具有灵活的签名,并且可以在一系列方法参数和返回值中进行选择。
@SchemaMapping
handler methods have flexible signatures and can choose from a range of
method arguments and return values..
Method Signature
Schema 映射处理程序方法可以具有以下任何方法参数:
Schema mapping handler methods can have any of the following method arguments:
Method Argument | Description |
---|---|
|
For access to a named field argument bound to a higher-level, typed Object. See |
|
For access to the raw argument value. See |
|
For access to a named field argument bound to a higher-level, typed Object along
with a flag to indicate if the input argument was omitted vs set to See |
|
For access to all field arguments bound to a higher-level, typed Object. See |
|
For access to the raw map of arguments. |
|
For access to field arguments through a project interface. |
"Source" |
For access to the source (i.e. parent/container) instance of the field. See Source. |
|
For access to pagination arguments. See Pagination, Scroll, |
|
For access to sort details. See Pagination, |
|
For access to a See |
|
For access to an attribute from the main |
|
For access to an attribute from the local |
|
For access to the context from the |
|
Obtained from the Spring Security context, if available. |
|
For access to |
|
For access to the selection set for the query through the |
|
For access to the |
|
For direct access to the underlying |
Schema 映射处理程序方法可以返回:
Schema mapping handler methods can return:
-
A resolved value of any type.
-
Mono
andFlux
for asynchronous value(s). Supported for controller methods and for anyDataFetcher
as described in ReactiveDataFetcher
. -
java.util.concurrent.Callable
to have the value(s) produced asynchronously. For this to work,AnnotatedControllerConfigurer
must be configured with anExecutor
.
@Argument
在 GraphQL Java 中,DataFetchingEnvironment
提供对特定于字段的参数值的映射的访问。这些值可以是简单的标量值(例如 String、Long)、用于更复杂的输入的 Map
或值的 List
。
In GraphQL Java, DataFetchingEnvironment
provides access to a map of field-specific
argument values. The values can be simple scalar values (e.g. String, Long), a Map
of
values for more complex input, or a List
of values.
使用 @Argument
注解使一个参数绑定到目标对象并注入到处理程序方法中。绑定是通过将参数值映射到预期的使用方法参数类型的基本数据构造函数或使用默认构造函数创建对象然后将参数值映射到其属性来执行的。这会递归重复,使用所有嵌套参数值,并相应地创建嵌套目标对象。例如:
Use the @Argument
annotation to have an argument bound to a target object and
injected into the handler method. Binding is performed by mapping argument values to a
primary data constructor of the expected method parameter type, or by using a default
constructor to create the object and then map argument values to its properties. This is
repeated recursively, using all nested argument values and creating nested target objects
accordingly. For example:
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}
@MutationMapping
public Book addBook(@Argument BookInput bookInput) {
// ...
}
}
如果目标对象没有 setter,并且您无法更改它,则可以使用 |
If the target object doesn’t have setters, and you can’t change that, you can use a
property on |
如果没有给出 method 参数名(Java 8 以上版本中需要使用 -parameters
编译器标记,或者要求编译器的调试信息),那么将使用它来查找参数。如果需要,您可以通过注释来自定义名称,例如:@Argument("bookInput")
.
By default, if the method parameter name is available (requires the -parameters
compiler
flag with Java 8+ or debugging info from the compiler), it is used to look up the argument.
If needed, you can customize the name through the annotation, e.g. @Argument("bookInput")
.
|
The |
如果绑定失败,则会抛出一个 BindException
,它会将绑定问题积累为 field 错误,其中每个错误的 field
都是出现问题后的参数路径。
If binding fails, a BindException
is raised with binding issues accumulated as field
errors where the field
of each error is the argument path where the issue occurred.
使用一个 Map<String, Object>
参数,并配以 @Argument
,就能获取参数的原始值。例如:
You can use @Argument
with a Map<String, Object>
argument, to obtain the raw value of
the argument. For example:
@Controller
public class BookController {
@MutationMapping
public Book addBook(@Argument Map<String, Object> bookInput) {
// ...
}
}
在 1.2 之前,如果注释未指定名称, |
Prior to 1.2, |
ArgumentValue
默认情况下,GraphQL 中的输入参数是可空的和可选的,这意味着可以在 null
文字中设置参数,或者根本不提供参数。这种区分对部分更新很有用,其中底层数据也可以相应地设置成 null
或根本不更改。当使用 xref:controllers.adoc#controllers.schema-mapping.argument[@Argument
时,无法做出这种区分,因为在两种情况下你都会得到 null
或一个空 Optional
。
By default, input arguments in GraphQL are nullable and optional, which means an argument
can be set to the null
literal, or not provided at all. This distinction is useful for
partial updates with a mutation where the underlying data may also be, either set to
null
or not changed at all accordingly. When using @Argument
there is no way to make such a distinction, because you would get null
or an empty
Optional
in both cases.
如果您想了解一个值是否根本没有提供,则可以声明一个 ArgumentValue
方法参数,它是一个结果值的一个简单容器,并带有标志,以表示输入参数是否全部都已省略。您可以使用它来代替 @Argument
,在这种情况下,参数名将根据方法参数名来决定,或者与 @Argument
一起使用来指定参数名。
If you want to know not whether a value was not provided at all, you can declare an
ArgumentValue
method parameter, which is a simple container for the resulting value,
along with a flag to indicate whether the input argument was omitted altogether. You
can use this instead of @Argument
, in which case the argument name is determined from
the method parameter name, or together with @Argument
to specify the argument name.
例如:
For example:
@Controller
public class BookController {
@MutationMapping
public void addBook(ArgumentValue<BookInput> bookInput) {
if (!bookInput.isOmitted()) {
BookInput value = bookInput.value();
// ...
}
}
}
ArgumentValue
也被支持为一个 @Argument
方法参数的对象结构中的一个字段,可以经由一个构造器参数或一个 setter 来初始化,包括作为位于顶层对象下任何级别的对象的字段。
ArgumentValue
is also supported as a field within the object structure of an @Argument
method parameter, either initialized via a constructor argument or via a setter, including
as a field of an object nested at any level below the top level object.
@Arguments
如果您想将完整的参数映射捆绑到一个单独的目标对象上,而不是捆绑一个特定的带名字的参数,那么请使用 @Arguments
注释。
Use the @Arguments
annotation, if you want to bind the full arguments map onto a single
target Object, in contrast to @Argument
, which binds a specific, named argument.
例如,@Argument BookInput bookInput
使用参数 "bookInput" 的值来初始化 BookInput
,而 @Arguments
使用完整的参数映射,在这种情况下,顶层参数将被捆绑到 BookInput
属性。
For example, @Argument BookInput bookInput
uses the value of the argument "bookInput"
to initialize BookInput
, while @Arguments
uses the full arguments map and in that
case, top-level arguments are bound to BookInput
properties.
使用一个 Map<String, Object>
参数,并配以 @Arguments
,就能获取所有参数值的原始映射。
You can use @Arguments
with a Map<String, Object>
argument, to obtain the raw map of
all argument values.
@ProjectedPayload
Interface
作为使用具有 @Argument
的完整对象的备选方案,您还可以使用投影接口通过明确定义的最小接口访问 GraphQL 请求参数。当 Spring Data 位于类路径中时,参数投影由 Spring Data’s Interface projections 提供。
As an alternative to using complete Objects with @Argument
,
you can also use a projection interface to access GraphQL request arguments through a
well-defined, minimal interface. Argument projections are provided by
Spring Data’s Interface projections
when Spring Data is on the class path.
要使用此方法,请创建一个带有 @ProjectedPayload
注释的接口,并将其声明为一个控制器方法参数。如果参数带 @Argument
注释了,那么它将适用于 DataFetchingEnvironment.getArguments()
映射中的单个参数。当在没有 @Argument
的情况下声明该映射时,该投影将在完整的参数映射中的顶层参数上起作用。
To make use of this, create an interface annotated with @ProjectedPayload
and declare
it as a controller method parameter. If the parameter is annotated with @Argument
,
it applies to an individual argument within the DataFetchingEnvironment.getArguments()
map. When declared without @Argument
, the projection works on top-level arguments in
the complete arguments map.
例如:
For example:
@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
提供对字段的来源(即父级/容器)实例的访问权限。要访问此方法,只需声明一个带有目标类型的预期方法参数。
In GraphQL Java, the DataFetchingEnvironment
provides access to the source (i.e.
parent/container) instance of the field. To access this, simply declare a method parameter
of the expected target type.
@Controller
public class BookController {
@SchemaMapping
public Author author(Book book) {
// ...
}
}
源方法参数也有助于确定映射的类型名称。如果 Java 类的简单名称与 GraphQL 类型匹配,那么不需要在 @SchemaMapping
注释中显式指定类型名称。
The source method argument also helps to determine the type name for the mapping.
If the simple name of the Java class matches the GraphQL type, then there is no need to
explicitly specify the type name in the @SchemaMapping
annotation.
A |
Subrange
当 Spring 配置中有一个 xref:request-execution.adoc#execution.pagination.cursor.strategy[CursorStrategy
bean 时,控制器方法支持 Subrange<P>
参数,其中 <P>
是从游标转换后的相对位置。对于 Spring Data,ScrollSubrange
引出 ScrollPosition
。例如:
When there is a CursorStrategy
bean in Spring configuration,
controller methods support a Subrange<P>
argument where <P>
is a relative position
converted from a cursor. For Spring Data, ScrollSubrange
exposes ScrollPosition
.
For example:
@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。
See Pagination for an overview of pagination and of built-in mechanisms.
Sort
当 Spring 配置中存在 SortStrategy
bean 时,控制器方法支持将 Sort
作为方法参数。例如:
When there is a SortStrategy bean in Spring configuration, controller
methods support Sort
as a method argument. For example:
@Controller
public class BookController {
@QueryMapping
public Window<Book> books(Optional<Sort> optionalSort) {
Sort sort = optionalSort.orElse(Sort.by(..));
}
}
DataLoader
当为实体注册批量加载函数(如 Batch Loading 中所述)时,可以通过声明 DataLoader
类型的 method 参数来访问该实体的 DataLoader
,并使用它来加载该实体:
When you register a batch loading function for an entity, as explained in
Batch Loading, you can access the DataLoader
for the entity by declaring a
method argument of type DataLoader
and use it to load the entity:
@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
方法参数解析器还会尝试使用方法参数名作为密钥,但一般来说,这是没有必要的。
By default, BatchLoaderRegistry
uses the full class name of the value type (e.g. the
class name for Author
) for the key of the registration, and therefore simply declaring
the DataLoader
method argument with generic types provides enough information
to locate it in the DataLoaderRegistry
. As a fallback, the DataLoader
method argument
resolver will also try the method argument name as the key but typically that should not
be necessary.
请注意,对于许多与加载相关实体的情况,其中 @SchemaMapping
仅仅委派给 DataLoader
,你可以通过使用下一节中所述的 @BatchMapping
方法来减少样板代码。
Note that for many cases with loading related entities, where the @SchemaMapping
simply
delegates to a DataLoader
, you can reduce boilerplate by using a
@BatchMapping method as described in the next section.
Validation
当找到 javax.validation.Validator
bean 时, AnnotatedControllerConfigurer
允许在带注释的控制器方法上使用 Bean Validation。通常,bean 的类型为 LocalValidatorFactoryBean
。
When a javax.validation.Validator
bean is found, AnnotatedControllerConfigurer
enables support for
Bean Validation
on annotated controller methods. Typically, the bean is of type LocalValidatorFactoryBean
.
Bean Validation 让你可以声明类型约束:
Bean validation lets you declare constraints on types:
public class BookInput {
@NotNull
private String title;
@NotNull
@Size(max=13)
private String isbn;
}
然后,你可以在方法调用之前使用 @Valid
注释控制器方法参数,对其进行验证:
You can then annotate a controller method parameter with @Valid
to validate it before
method invocation:
@Controller
public class BookController {
@MutationMapping
public Book addBook(@Argument @Valid BookInput bookInput) {
// ...
}
}
如果在验证过程中发生错误,则会引发 ConstraintViolationException
。你可以使用 Exceptions
链来决定如何将其显示给客户端,方法是将其转换为包含在 GraphQL 响应中的错误。
If an error occurs during validation, a ConstraintViolationException
is raised.
You can use the Exceptions chain to decide how to present that to clients
by turning it into an error to include in the GraphQL response.
除 |
In addition to |
Bean 验证适用于 @Argument`
、`
@Arguments`` 和 @ProjectedPayload
方法参数,但更普遍地适用于任何方法参数。
Bean validation is useful for @Argument
,
@Arguments
, and
@ProjectedPayload
method parameters, but applies more generally to any method parameter.
Hibernate Validator 与 Kotlin 协程方法不兼容,在内省其方法参数时会失败。请参阅 spring-projects/spring-graphql#344 (comment) 以获取相关问题的链接和建议的解决方法。
Hibernate Validator is not compatible with Kotlin Coroutine methods and fails when introspecting their method parameters. Please see spring-projects/spring-graphql#344 (comment) for links to relevant issues and a suggested workaround.
@BatchMapping
Batch Loading
通过使用 org.dataloader.DataLoader
来延迟加载各个实体实例,以便可以将它们一起加载,从而解决了 N+1 选择问题。例如:
Batch Loading addresses the N+1 select problem through the use of an
org.dataloader.DataLoader
to defer the loading of individual entity instances, so they
can be loaded together. For example:
@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
方法避免的样板。例如:
For the straight-forward case of loading an associated entity, shown above, the
@SchemaMapping
method does nothing more than delegate to the DataLoader
. This is
boilerplate that can be avoided with a @BatchMapping
method. For example:
@Controller
public class BookController {
@BatchMapping
public Mono<Map<Book, Author>> author(List<Book> books) {
// ...
}
}
上面的代码在 BatchLoaderRegistry
中成为一个批处理加载函数,其中键为 Book
实例,加载的值为其作者。此外,还将`DataFetcher` 透明地绑定到 Book
类型的 author
字段,它只是给定其源/父 Book
实例,委派给作者的`DataLoader`。
The above becomes a batch loading function in the BatchLoaderRegistry
where keys are Book
instances and the loaded values their authors. In addition, a
DataFetcher
is also transparently bound to the author
field of the type Book
, which
simply delegates to the DataLoader
for authors, given its source/parent Book
instance.
为了用作唯一键, To be used as a unique key, |
默认情况下,字段名称默认为方法名称,而类型名称默认为输入 List
元素类型的简单类名称。两者都可以通过注释属性自定义。类型名称也可以从类级别的`@SchemaMapping` 继承。
By default, the field name defaults to the method name, while the type name defaults to
the simple class name of the input List
element type. Both can be customized through
annotation attributes. The type name can also be inherited from a class level
@SchemaMapping
.
Method Signature
批处理映射方法支持以下参数:
Batch mapping methods support the following arguments:
Method Argument | Description |
---|---|
|
The source/parent objects. |
|
Obtained from Spring Security context, if available. |
|
For access to a value from the |
|
For access to the context from the |
|
The environment that is available in GraphQL Java to a
|
批处理映射方法可以返回:
Batch mapping methods can return:
Return Type | Description |
---|---|
|
A map with parent objects as keys, and batch loaded objects as values. |
|
A sequence of batch loaded objects that must be in the same order as the source/parent objects passed into the method. |
|
Imperative variants, e.g. without remote calls to make. |
|
Imperative variants to be invoked asynchronously. For this to work,
|
@GraphQlExceptionHandler
使用 @GraphQlExceptionHandler
方法来使用灵活的 method signature
处理数据获取异常。当在控制器中声明时,异常处理程序方法适用于来自同一控制器的异常:
Use @GraphQlExceptionHandler
methods to handle exceptions from data fetching with a
flexible method signature. When declared in a
controller, exception handler methods apply to exceptions from the same controller:
@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
中声明时,异常处理程序方法应用于所有控制器:
When declared in an @ControllerAdvice
, exception handler methods apply across controllers:
@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
中。
Exception handling via @GraphQlExceptionHandler
methods is applied automatically to
controller invocations. To handle exceptions from other graphql.schema.DataFetcher
implementations, not based on controller methods, obtain a
DataFetcherExceptionResolver
from AnnotatedControllerConfigurer
, and register it in
GraphQlSource.Builder
as a DataFetcherExceptionResolver.
Method Signature
异常处理方法支持灵活的方法签名,其中方法参数从 DataFetchingEnvironment,
中解析,并与 @SchemaMapping methods
的方法参数匹配。
Exception handler methods support a flexible method signature with method arguments
resolved from a DataFetchingEnvironment,
and matching to those of
@SchemaMapping methods.
支持的返回类型如下列出:
Supported return types are listed below:
Return Type | Description |
---|---|
|
Resolve the exception to a single field error. |
|
Resolve the exception to multiple field errors. |
|
Resolve the exception without response errors. |
|
Resolve the exception to a single error, to multiple errors, or none.
The return value must be |
|
For asynchronous resolution where |