此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring GraphQL 1.3.4! |
带注释的控制器
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” 的查询。
声明
您可以定义@Controller
beans 作为标准的 Spring bean 定义。这@Controller
stereotype 允许自动检测,与 Spring 通用一致
支持检测@Controller
和@Component
类路径和
为它们自动注册 bean 定义。它还充当带注释的
类,指示其在 GraphQL 应用程序中作为数据获取组件的角色。
AnnotatedControllerConfigurer
检测@Controller
bean 并注册其
注解的处理程序方法为DataFetcher
S 通过RuntimeWiring.Builder
.它是一个
实现RuntimeWiringConfigurer
可以添加到GraphQlSource.Builder
.
Boot Starter 会自动声明AnnotatedControllerConfigurer
作为 Bean
并添加所有RuntimeWiringConfigurer
beans 设置为GraphQlSource.Builder
这使得
支持 AnnotatedDataFetcher
,请参阅 GraphQL RuntimeWiring 部分
在 Boot Starter 文档中。
@SchemaMapping
这@SchemaMapping
annotation 将处理程序方法映射到 GraphQL 架构中的字段
并声明它是DataFetcher
对于该字段。注解可以指定
父类型名称,以及字段名称:
@Controller
public class BookController {
@SchemaMapping(typeName="Book", field="author")
public Author getAuthor(Book book) {
// ...
}
}
这@SchemaMapping
annotation 也可以省略这些属性,在这种情况下,
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
的值。
使用@Argument
annotation 将参数绑定到目标对象,并将
注入到 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")
.
这@Argument annotation 没有 “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 的可选参数,即参数
可以设置为null
literal 的 Literal 或根本不提供。这种区别对于
部分更新,其中底层数据也可能是null
或者根本没有相应地改变。使用@Argument
没有办法做出这样的区分,因为你会得到null
或空的Optional
在这两种情况下。
如果你想知道某个值是否根本没有提供,你可以声明一个ArgumentValue
method 参数,该参数是结果值的简单容器
以及一个标志,以指示是否完全省略了 input 参数。你
可以使用 this 而不是@Argument
,在这种情况下,参数名称由
方法参数名称,或者与@Argument
以指定参数名称。
例如:
@Controller
public class BookController {
@MutationMapping
public void addBook(ArgumentValue<BookInput> bookInput) {
if (!bookInput.isOmitted()) {
BookInput value = bookInput.value();
// ...
}
}
}
ArgumentValue
也支持作为@Argument
method 参数,可以通过构造函数参数或通过 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
当存在CursorStrategy
Spring 配置中的 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
) 作为注册的密钥,因此只需声明
这DataLoader
method 参数提供足够的信息
要在DataLoaderRegistry
.作为回退,DataLoader
method 参数
Resolver 还会尝试将方法参数 name 作为键,但通常不应这样做
是必要的。
请注意,对于加载相关实体的许多情况,其中@SchemaMapping
只是
delegates 传递给DataLoader
,您可以使用@BatchMapping方法减少样板,如下一节所述。
验证
当javax.validation.Validator
bean 的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 时,异常处理程序方法适用于来自同一控制器的异常:
@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
实现,而不是基于 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
Annotate the controller with @SchemaMapping
and a typeName
attribute, to avoid repeating it on methods
2
Define a @QueryMapping
for the "music" namespace
3
The "music" query returns an "empty" record, but could also return an empty map
4
Queries are now declared as fields under the "MusicQueries" type
Instead of declaring wrapping types ("MusicQueries", "UserQueries") explicitly in controllers,
you can choose to configure them with the runtime wiring using a GraphQlSourceBuilderCustomizer
with 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
List all the wrapper types for the "Query" type
2
Manually declare data fetchers for each of them, returning an empty Map