帶註解的控制器
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。
宣告
您可以將 @Controller bean 定義為標準的 Spring bean 定義。 @Controller 註解允許自動檢測,與 Spring 對類路徑上 @Controller 和 @Component 類的通用檢測和為其自動註冊 bean 定義的支援保持一致。它還作為註解類的原型,表明其在 GraphQL 應用程式中作為資料獲取元件的角色。
AnnotatedControllerConfigurer 檢測 @Controller bean 並透過 RuntimeWiring.Builder 將其註解的處理方法註冊為 DataFetcher。它是 RuntimeWiringConfigurer 的實現,可以新增到 GraphQlSource.Builder。 Boot Starter 自動將 AnnotatedControllerConfigurer 宣告為一個 bean,並將所有 RuntimeWiringConfigurer bean 新增到 GraphQlSource.Builder,從而啟用對註解 DataFetcher 的支援,請參閱 Boot Starter 文件中的 GraphQL RuntimeWiring 部分。
@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 註解,並且 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 處理方法具有靈活的簽名,並且可以選擇各種方法引數和返回值。
方法引數
模式對映處理方法可以具有以下任何方法引數
| 方法引數 | 描述 |
|---|---|
|
用於訪問繫結到更高級別、型別化物件的命名欄位引數。 請參閱 |
|
用於訪問原始引數值。 請參閱 |
|
用於訪問繫結到更高級別、型別化物件的命名欄位引數,以及一個標誌以指示輸入引數是省略還是設定為 請參閱 |
|
用於訪問繫結到更高級別、型別化物件的所有欄位引數。 請參閱 |
|
用於訪問原始引數對映。 |
|
透過投影介面訪問欄位引數。 請參閱 |
"Source" |
用於訪問欄位的源(即父/容器)例項。 請參閱 Source。 |
|
用於訪問分頁引數。 |
|
用於訪問排序詳情。 |
|
用於訪問 請參閱 |
|
用於從 |
|
用於從 |
|
用於從 |
|
如果可用,則從 Spring Security 上下文獲取。 |
|
用於從 Spring Security 上下文訪問 |
|
透過 |
|
用於從 |
|
用於直接訪問底層 |
返回值
模式對映處理方法可以返回
| 返回值 | 描述 |
|---|---|
已解析的值 |
直接解析的任何應用程式型別。 |
|
用於非同步值。 支援控制器方法和任何 |
Kotlin |
它們會自動適配到 |
|
用於非同步生成值。為此, |
|
其中 這是“完整”的 GraphQL Java 返回值,不僅包含“資料”。用於透過“擴充套件”或 “本地上下文” 完成結果。 |
在 Java 21+ 上,當 AnnotatedControllerConfigurer 配置了 Executor 時,具有阻塞方法簽名的控制器方法會非同步呼叫。預設情況下,如果控制器方法不返回非同步型別(如 Flux、Mono、CompletableFuture),並且也不是 Kotlin 掛起函式,則認為它是阻塞的。您可以在 AnnotatedControllerConfigurer 上配置一個阻塞控制器方法 Predicate 以幫助確定哪些方法被認為是阻塞的。
當屬性 spring.threads.virtual.enabled 設定時,Spring for GraphQL 的 Spring Boot 啟動器會自動為虛擬執行緒配置 AnnotatedControllerConfigurer 和 Executor。 |
介面模式對映
當控制器方法對映到模式介面欄位時,預設情況下,該對映將被多個對映替換,每個實現該介面的模式物件型別一個。這允許對所有子型別使用一個控制器方法。
例如,給定
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 ActivityController {
@QueryMapping
public List<Activity> activities() {
// ...
}
@SchemaMapping
public User coordinator(Activity activity) {
// Called for any Activity subtype
}
}
如有必要,您可以接管單個子型別的對映
@Controller
public class ActivityController {
@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 提供對欄位特定引數值對映的訪問。這些值可以是簡單的標量值(例如 String、Long),用於更復雜輸入的 Map 值,或 List 值。
使用 @Argument 註解將引數繫結到目標物件並注入到處理方法中。如果目標物件不是簡單的 Java 型別,則透過主資料建構函式執行繫結,並透過使用巢狀引數值遞迴重複此操作,以呼叫巢狀目標物件的建構函式。例如
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}
@MutationMapping
public Book addBook(@Argument BookInput bookInput) {
// ...
}
}
或者,也可以透過首先呼叫預設建構函式,然後透過目標物件上的 setter 應用引數值來完成繫結。
| 如果目標物件沒有 setter,您可以配置引數繫結器以回退到透過直接欄位訪問進行繫結,請參閱 引數繫結。 |
預設情況下,如果方法引數名稱可用(例如在 Java 8+ 中使用 -parameters 編譯器標誌或來自編譯器的除錯資訊),則它用於查詢引數。如有必要,您可以透過註解自定義名稱,例如 @Argument("bookInput")。
請注意,@Argument 註解沒有“required”標誌,也沒有指定預設值的選項。這兩者都可以在 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> 始終返回原始引數值,無論是基於引數名稱還是註解中的名稱。要訪問完整的引數對映,請改用 @Arguments。 |
引數繫結
@Argument 繫結的支援由 GraphQlArgumentBinder 提供。此類的作用是從 GraphQL 引數值建立和填充目標物件。
引數繫結支援多種自定義選項
-
nameResolver— 自定義 GraphQL 引數名稱到物件屬性的對映,這對於處理命名約定(如使用“-”)很有用。 -
fallBackOnDirectFieldAccess— 如果目標物件不使用訪問器方法,則回退到直接欄位訪問。 -
conversionService— 在需要時用於型別轉換。
要自定義引數繫結,請使用 AnnotatedControllerConfigurer 上的專用屬性來設定 GraphQlArgumentBinder.Options。
ArgumentValue
預設情況下,GraphQL 中的輸入引數是可空且可選的,這意味著引數可以設定為 null 字面量,也可以根本不提供。這種區別對於使用突變的區域性更新很有用,其中底層資料也可以相應地設定為 null 或根本不更改。當使用 @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 初始化,包括作為頂層物件以下任何級別的巢狀物件的欄位。
客戶端也支援此功能,帶有專用的 Jackson 模組,請參閱 客戶端的 ArgumentValue 支援 部分。
@Arguments
如果您想將完整的引數對映繫結到一個目標物件,請使用 @Arguments 註解,這與繫結特定命名引數的 @Argument 不同。
例如,@Argument BookInput bookInput 使用引數“bookInput”的值來初始化 BookInput,而 @Arguments 使用完整的引數對映,在這種情況下,頂層引數繫結到 BookInput 屬性。
您可以將 @Arguments 與 Map<String, Object> 引數一起使用,以獲取所有引數值的原始對映。
@ProjectedPayload 介面
作為使用 @Argument 的完整物件的替代方案,您還可以使用投影介面透過定義良好的最小介面訪問 GraphQL 請求引數。當 Spring Data 位於類路徑上時,引數投影由 Spring Data 的介面投影 提供。
要使用此功能,請建立一個帶有 @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 配置中存在 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);
// ...
}
}
請參閱 分頁 以獲取分頁和內建機制的概述。
Sort
當 Spring 配置中存在 SortStrategy bean 時,控制器方法支援 Sort 作為方法引數。例如
@Controller
public class BookController {
@QueryMapping
public Window<Book> books(Optional<Sort> optionalSort) {
Sort sort = optionalSort.orElse(Sort.by(..));
}
}
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 方法來減少樣板程式碼,如下一節所述。
驗證
當找到 javax.validation.Validator bean 時,AnnotatedControllerConfigurer 為註解控制器方法啟用 Bean Validation 支援。通常,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。您可以使用 異常 鏈來決定如何透過將其轉換為要包含在 GraphQL 響應中的錯誤來呈現給客戶端。
除了 @Valid,您還可以使用 Spring 的 @Validated,它允許指定驗證組。 |
Bean 驗證對於 @Argument、@Arguments 和 @ProjectedPayload 方法引數很有用,但更普遍適用於任何方法引數。
|
驗證和 Kotlin 協程
Hibernate Validator 與 Kotlin 協程方法不相容,在內省其方法引數時會失敗。請參閱 spring-projects/spring-graphql#344 (comment) 以獲取相關問題連結和建議的解決方法。 |
HTTP 頭
要從控制器方法訪問 HTTP 頭,我們建議使用 WebGraphQlInterceptor 將感興趣的 HTTP 頭複製到 GraphQLContext 中,從那裡它們可以用於任何 DataFetcher,以及作為 @GraphQlContext 引數的註解控制器方法。
有一個內建的 HttpRequestHeaderInterceptor 可以幫助實現這一點。請參閱 Web 攔截器。
本地上下文
主 GraphQlContext 對整個查詢是全域性的,可用於儲存和檢索用於可觀察性、安全性等方面的跨領域上下文資料。有時您希望將附加資訊傳遞給子欄位資料獲取器,並避免汙染主上下文。對於此類用例,您應考慮使用本地 GraphQLContext,因為它僅限於資料獲取操作的子集。
控制器方法可以透過返回包含已解析資料和新上下文的 DataFetcherResult<T> 來提供本地上下文
@Controller
public class LocalContextBookController {
@QueryMapping
public DataFetcherResult<Book> bookById(@Argument Long id) {
// Our controller method must return a DataFetcherResult
DataFetcherResult.Builder<Book> resultBuilder = DataFetcherResult.newResult();
BookAndAuthor bookAndAuthor = this.fetchBookAndAuthorById(id);
// Create a new local context and store the author value
GraphQLContext localContext = GraphQLContext.getDefault()
.put("author", bookAndAuthor.author);
return resultBuilder
.data(bookAndAuthor.book)
.localContext(localContext)
.build();
}
@SchemaMapping
public List<Book> related(Book book, @LocalContextValue Author author) {
List<Book> relatedBooks = new ArrayList<>();
relatedBooks.addAll(fetchBooksByAuthor(author));
relatedBooks.addAll(fetchSimilarBooks(book));
return relatedBooks;
}
如果您想檢視更多詳細的示例和討論,請參閱 graphql-java 文件中“透過前瞻構建高效資料獲取器”部分。
@BatchMapping
批次載入 透過使用 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 欄位,它只是將請求委託給作者的 DataLoader,給定其源/父 Book 例項。
|
要用作唯一鍵, |
預設情況下,欄位名稱預設為方法名稱,而型別名稱預設為輸入 List 元素型別的簡單類名。兩者都可以透過註解屬性進行自定義。型別名稱也可以從類級別的 @SchemaMapping 繼承。
|
|
方法引數
批次對映方法支援以下引數
| 方法引數 | 描述 |
|---|---|
|
源/父物件。 |
|
如果可用,則從 Spring Security 上下文獲取。 |
|
用於從 |
|
用於從 |
|
GraphQL Java 中可用於
|
返回值
批次對映方法可以返回
| 返回型別 | 描述 |
|---|---|
|
一個以父物件為鍵,批次載入物件為值的對映。 |
|
一個批次載入物件的序列,其順序必須與傳遞給方法的源/父物件的順序相同。 |
|
命令式變體,例如沒有遠端呼叫的情況。 |
|
命令式變體,非同步呼叫。為此, |
帶有 |
適配為 |
在 Java 21+ 上,當 AnnotatedControllerConfigurer 配置了 Executor 時,具有阻塞方法簽名的控制器方法會非同步呼叫。預設情況下,如果控制器方法不返回非同步型別(如 Flux、Mono、CompletableFuture),並且也不是 Kotlin 掛起函式,則認為它是阻塞的。您可以在 AnnotatedControllerConfigurer 上配置一個阻塞控制器方法 Predicate 以幫助確定哪些方法被認為是阻塞的。
當屬性 spring.threads.virtual.enabled 設定時,Spring for GraphQL 的 Spring Boot 啟動器會自動為虛擬執行緒配置 AnnotatedControllerConfigurer 和 Executor。 |
介面批次對映
與 介面模式對映 一樣,當批次對映方法對映到模式介面欄位時,該對映將被多個對映替換,每個實現該介面的模式物件型別一個。
這意味著,給定以下內容
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 方法處理資料獲取中的異常,並具有靈活的 方法簽名。當在控制器中宣告時,異常處理方法適用於來自同一控制器的異常
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 實現(非基於控制器方法)的異常,請從 AnnotatedControllerConfigurer 獲取 DataFetcherExceptionResolver,並將其註冊到 GraphQlSource.Builder 中作為 DataFetcherExceptionResolver。
方法簽名
異常處理方法支援靈活的方法簽名,其方法引數從 DataFetchingEnvironment 解析,並與 @SchemaMapping 方法 的引數匹配。
支援的返回型別如下
| 返回型別 | 描述 |
|---|---|
|
將異常解析為單個欄位錯誤。 |
|
將異常解析為多個欄位錯誤。 |
|
解析異常,不帶響應錯誤。 |
|
將異常解析為單個錯誤、多個錯誤或無錯誤。返回值必須是 |
|
用於非同步解析,其中 |
名稱空間
在模式級別,查詢和突變操作直接在 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 | 為“music”名稱空間定義 @QueryMapping |
| 3 | “music”查詢返回一個“空”記錄,但也可能返回一個空對映 |
| 4 | 查詢現在被宣告為“MusicQueries”型別下的欄位 |
您可以在執行時透過 GraphQlSourceBuilderCustomizer 在 Spring Boot 中配置包裝型別(“MusicQueries”、“UserQueries”),而無需在控制器中顯式宣告它們
import java.util.Collections;
import java.util.List;
import org.springframework.boot.graphql.autoconfigure.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 | 手動為每個型別宣告資料獲取器,返回一個空對映 |