宣告式註解基礎快取

對於快取宣告,Spring 的快取抽象化提供了一組 Java 註解

  • @Cacheable:觸發快取填充。

  • @CacheEvict:觸發快取逐出。

  • @CachePut:更新快取,而不會干擾方法執行。

  • @Caching:重新組合要應用於方法的數個快取操作。

  • @CacheConfig:在類別層級共用一些常見的快取相關設定。

@Cacheable 註解

顧名思義,您可以使用 @Cacheable 來標記可快取的方法 — 也就是說,結果會儲存在快取中的方法,以便在後續調用(使用相同的引數)時,傳回快取中的值,而無需實際調用方法。 在其最簡單的形式中,註解宣告需要與註解方法關聯的快取名稱,如下列範例所示

@Cacheable("books")
public Book findBook(ISBN isbn) {...}

在前面的程式碼片段中,findBook 方法與名為 books 的快取相關聯。 每次調用該方法時,都會檢查快取,以查看調用是否已執行,且不必重複執行。 雖然在大多數情況下,只宣告一個快取,但註解允許指定多個名稱,以便使用多個快取。 在這種情況下,在調用方法之前,會檢查每個快取 — 如果至少命中一個快取,則傳回關聯的值。

所有其他不包含值的快取也會更新,即使快取方法實際上並未調用。

以下範例在具有多個快取的 findBook 方法上使用 @Cacheable

@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}

預設金鑰產生

由於快取本質上是鍵值儲存,因此每次調用快取方法都需要轉換為適合快取存取的金鑰。 快取抽象化使用基於以下演算法的簡單 KeyGenerator

  • 如果未給定參數,則傳回 SimpleKey.EMPTY

  • 如果僅給定一個參數,則傳回該實例。

  • 如果給定多個參數,則傳回包含所有參數的 SimpleKey

只要參數具有自然金鑰並實作有效的 hashCode()equals() 方法,此方法在大多數使用案例中都能良好運作。 如果不是這種情況,您需要變更策略。

若要提供不同的預設金鑰產生器,您需要實作 org.springframework.cache.interceptor.KeyGenerator 介面。

預設金鑰產生策略已在 Spring 4.0 版本中變更。 早期版本的 Spring 使用的金鑰產生策略對於多個金鑰參數,僅考慮參數的 hashCode() 而非 equals()。 這可能會導致意外的金鑰衝突(請參閱 spring-framework#14870 以取得背景資訊)。 新的 SimpleKeyGenerator 在這種情況下使用複合金鑰。

如果您想繼續使用先前的金鑰策略,您可以組態已棄用的 org.springframework.cache.interceptor.DefaultKeyGenerator 類別,或建立自訂的基於雜湊的 KeyGenerator 實作。

自訂金鑰產生宣告

由於快取是通用的,目標方法很可能具有各種簽章,這些簽章無法輕易對映到快取結構之上。 當目標方法具有多個引數時,這往往會變得顯而易見,其中只有一些引數適用於快取(而其餘引數僅供方法邏輯使用)。 請考慮以下範例

@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

乍看之下,雖然兩個 boolean 引數會影響書籍的查找方式,但它們對快取沒有用處。 此外,如果只有其中一個重要,而另一個不重要,該怎麼辦?

對於這種情況,@Cacheable 註解可讓您透過其 key 屬性指定金鑰的產生方式。 您可以使用 SpEL 來選取感興趣的引數(或其巢狀屬性)、執行操作,甚至調用任意方法,而無需編寫任何程式碼或實作任何介面。 相較於 預設產生器,這是建議的方法,因為隨著程式碼庫的成長,方法的簽章往往會大相逕庭。 雖然預設策略可能適用於某些方法,但很少適用於所有方法。

以下範例使用各種 SpEL 宣告(如果您不熟悉 SpEL,請務必閱讀 Spring 運算式語言

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

先前的程式碼片段顯示選取特定引數、其屬性之一,甚至是任意(靜態)方法有多容易。

如果負責產生金鑰的演算法太過特定,或需要共用,您可以在操作上定義自訂 keyGenerator。 若要執行此操作,請指定要使用的 KeyGenerator Bean 實作的名稱,如下列範例所示

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
keykeyGenerator 參數互斥,並且指定兩者的操作會導致例外狀況。

預設快取解析

快取抽象化使用簡單的 CacheResolver,透過使用已組態的 CacheManager 擷取在操作層級定義的快取。

若要提供不同的預設快取解析器,您需要實作 org.springframework.cache.interceptor.CacheResolver 介面。

自訂快取解析

預設快取解析非常適合使用單一 CacheManager 且沒有複雜快取解析需求的應用程式。

對於使用多個快取管理器的應用程式,您可以為每個操作設定要使用的 cacheManager,如下列範例所示

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") (1)
public Book findBook(ISBN isbn) {...}
1 指定 anotherCacheManager

您也可以完全取代 CacheResolver,其方式與取代 金鑰產生 類似。 每個快取操作都會要求解析,讓實作實際上根據執行階段引數解析要使用的快取。 以下範例顯示如何指定 CacheResolver

@Cacheable(cacheResolver="runtimeCacheResolver") (1)
public Book findBook(ISBN isbn) {...}
1 指定 CacheResolver

由於 Spring 4.1,快取註解的 value 屬性不再是強制性的,因為此特定資訊可以由 CacheResolver 提供,而與註解的內容無關。

keykeyGenerator 類似,cacheManagercacheResolver 參數互斥,並且指定兩者的操作會導致例外狀況,因為自訂 CacheManager 會被 CacheResolver 實作忽略。 這可能不是您所期望的。

同步快取

在多執行緒環境中,某些操作可能會針對相同的引數同時調用(通常在啟動時)。 依預設,快取抽象化不會鎖定任何內容,並且相同的值可能會計算多次,從而破壞快取的目的。

對於這些特定情況,您可以使用 sync 屬性來指示基礎快取提供者在計算值時鎖定快取項目。 因此,只有一個執行緒忙於計算值,而其他執行緒則會被封鎖,直到快取中的項目更新為止。 以下範例顯示如何使用 sync 屬性

@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 使用 sync 屬性。
這是一項選用功能,您喜愛的快取程式庫可能不支援它。 核心框架提供的所有 CacheManager 實作都支援它。 如需更多詳細資訊,請參閱您的快取提供者的文件。

使用 CompletableFuture 和 Reactive 傳回類型進行快取

從 6.1 開始,快取註解會將 CompletableFuture 和 Reactive 傳回類型納入考量,並自動調整快取互動以符合需求。

對於傳回 CompletableFuture 的方法,只要物件完成,就會快取該 Future 產生的物件,並且快取命中的快取查找將透過 CompletableFuture 擷取

@Cacheable("books")
public CompletableFuture<Book> findBook(ISBN isbn) {...}

對於傳回 Reactor Mono 的方法,只要 Reactive Streams 發佈者發出的物件可用,就會快取該物件,並且快取命中的快取查找將擷取為 Mono(由 CompletableFuture 支援)

@Cacheable("books")
public Mono<Book> findBook(ISBN isbn) {...}

對於傳回 Reactor Flux 的方法,Reactive Streams 發佈者發出的物件將收集到 List 中,並在該列表完成時快取,並且快取命中的快取查找將擷取為 Flux(由快取 List 值的 CompletableFuture 支援)

@Cacheable("books")
public Flux<Book> findBooks(String author) {...}

這種 CompletableFuture 和 Reactive 適配也適用於同步快取,在發生並行快取未命中時僅計算一次值

@Cacheable(cacheNames="foos", sync=true) (1)
public CompletableFuture<Foo> executeExpensiveOperation(String id) {...}
1 使用 sync 屬性。
為了使這種安排在執行階段運作,已組態的快取需要能夠進行基於 CompletableFuture 的擷取。 Spring 提供的 ConcurrentMapCacheManager 會自動適應這種擷取樣式,而 CaffeineCacheManager 在啟用其非同步快取模式時原生支援它:在您的 CaffeineCacheManager 實例上設定 setAsyncCacheMode(true)
@Bean
CacheManager cacheManager() {
	CaffeineCacheManager cacheManager = new CaffeineCacheManager();
	cacheManager.setCacheSpecification(...);
	cacheManager.setAsyncCacheMode(true);
	return cacheManager;
}

最後但並非最不重要的一點是,請注意,註解驅動的快取不適用於涉及組合和背壓的複雜 Reactive 互動。 如果您選擇在特定的 Reactive 方法上宣告 @Cacheable,請考慮相當粗略的快取互動的影響,它只會儲存 Mono 發出的物件,甚至是 Flux 的預先收集的物件列表。

條件式快取

有時,方法可能並不總是適合快取(例如,它可能取決於給定的引數)。 快取註解透過 condition 參數支援這種使用案例,該參數採用 SpEL 運算式,該運算式評估為 truefalse。 如果為 true,則快取方法。 如果不是,它的行為就像方法未快取一樣(也就是說,每次都調用該方法,無論快取中有什麼值或使用什麼引數)。 例如,只有當引數 name 的長度小於 32 時,才會快取以下方法

@Cacheable(cacheNames="book", condition="#name.length() < 32") (1)
public Book findBook(String name)
1 @Cacheable 上設定條件。

除了 condition 參數之外,您可以使用 unless 參數來否決將值新增至快取。 與 condition 不同,unless 運算式會在方法調用後評估。 為了擴展先前的範例,也許我們只想快取平裝書,如下列範例所示

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") (1)
public Book findBook(String name)
1 使用 unless 屬性來封鎖精裝書。

快取抽象化支援 java.util.Optional 傳回類型。 如果 Optional存在,則會將其儲存在關聯的快取中。 如果 Optional 值不存在,則 null 將儲存在關聯的快取中。 #result 始終指向業務實體,而不是支援的包裝函式,因此先前的範例可以改寫如下

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)

請注意,#result 仍然指向 Book 而不是 Optional<Book>。 由於它可能是 null,因此我們使用 SpEL 的 安全導航運算子

可用的快取 SpEL 評估 Context

每個 SpEL 運算式都針對專用的 context 進行評估。 除了內建參數之外,框架還提供專用的快取相關中繼資料,例如引數名稱。 下表描述了 Context 中可用的項目,以便您可以將它們用於金鑰和條件計算

表 1. SpEL 運算式中可用的快取中繼資料
名稱 位置 描述 範例

methodName

根物件

正在調用的方法的名稱

#root.methodName

method

根物件

正在呼叫的方法

#root.method.name

target

根物件

正在呼叫的目標物件

#root.target

targetClass

根物件

正在呼叫的目標的類別

#root.targetClass

args

根物件

用於呼叫目標的引數(作為物件陣列)

#root.args[0]

caches

根物件

執行目前方法所針對的快取集合

#root.caches[0].name

引數名稱

評估環境

特定方法引數的名稱。如果名稱不可用(例如,因為程式碼編譯時未使用 -parameters 旗標),也可以使用 #a<#arg> 語法來取得個別引數,其中 <#arg> 代表引數索引(從 0 開始)。

#iban#a0(您也可以使用 #p0#p<#arg> 標記法作為別名)。

result

評估環境

方法呼叫的結果(要快取的數值)。僅在 unless 表達式、「快取放入」表達式(用於計算 key)或「快取逐出」表達式(當 beforeInvocationfalse 時)中可用。對於支援的包裝器(例如 Optional),#result 指的是實際物件,而非包裝器。

#result

@CachePut 註解

當快取需要更新而不會干擾方法執行時,您可以使用 @CachePut 註解。也就是說,方法總是會被呼叫,並且其結果會放入快取中(根據 @CachePut 選項)。它支援與 @Cacheable 相同的選項,並且應該用於快取填充,而不是方法流程最佳化。以下範例使用 @CachePut 註解

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
通常強烈建議不要在同一個方法上同時使用 @CachePut@Cacheable 註解,因為它們具有不同的行為。後者會導致方法調用因使用快取而被跳過,而前者則強制調用以執行快取更新。這會導致意外的行為,並且除了特定的邊角案例(例如註解具有彼此排除的條件)之外,應避免此類宣告。另請注意,此類條件不應依賴結果物件(即 #result 變數),因為這些條件會預先驗證以確認排除。

從 6.1 版本開始,@CachePut 會將 CompletableFuture 和反應式傳回類型納入考量,在產生的物件可用時執行放入操作。

@CacheEvict 註解

快取抽象化不僅允許填充快取儲存區,還允許逐出。此過程對於從快取中移除過時或未使用的資料非常有用。與 @Cacheable 相反,@CacheEvict 標記執行快取逐出的方法(即,作為從快取中移除資料的觸發器的方法)。與其同級註解類似,@CacheEvict 需要指定一個或多個受動作影響的快取,允許指定自訂快取和鍵值解析或條件,並具有一個額外參數(allEntries),指示是否需要執行快取範圍逐出,而不是僅執行條目逐出(基於鍵值)。以下範例從 books 快取中逐出所有條目

@CacheEvict(cacheNames="books", allEntries=true) (1)
public void loadBooks(InputStream batch)
1 使用 allEntries 屬性從快取中逐出所有條目。

當需要清除整個快取區域時,此選項非常方便。與逐出每個條目(這將花費很長時間,因為效率不高)不同,所有條目都在一個操作中移除,如前面的範例所示。請注意,在這種情況下,框架會忽略任何指定的鍵值,因為它不適用(整個快取被逐出,而不僅僅是一個條目)。

您也可以使用 beforeInvocation 屬性來指示逐出應在方法呼叫之後(預設)還是之前發生。前者提供與其餘註解相同的語意:一旦方法成功完成,就會在快取上執行一個動作(在本例中為逐出)。如果方法未執行(因為它可能被快取)或拋出例外,則不會發生逐出。後者(beforeInvocation=true)會導致逐出始終在方法呼叫之前發生。這在逐出不需要繫結到方法結果的情況下非常有用。

請注意,void 方法可以與 @CacheEvict 一起使用 - 因為這些方法充當觸發器,所以傳回值會被忽略(因為它們不與快取互動)。@Cacheable 則不是這種情況,它會將資料新增到快取或更新快取中的資料,因此需要結果。

從 6.1 版本開始,@CacheEvict 會將 CompletableFuture 和反應式傳回類型納入考量,在處理完成後執行 invocation 後逐出操作。

@Caching 註解

有時,需要指定多個相同類型的註解(例如 @CacheEvict@CachePut)— 例如,因為不同快取之間的條件或鍵值表達式不同。@Caching 允許在同一個方法上使用多個巢狀 @Cacheable@CachePut@CacheEvict 註解。以下範例使用兩個 @CacheEvict 註解

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

@CacheConfig 註解

到目前為止,我們已經看到快取操作提供了許多自訂選項,您可以為每個操作設定這些選項。但是,如果某些自訂選項適用於類別的所有操作,則配置這些選項可能會很繁瑣。例如,指定類別的每個快取操作要使用的快取名稱可以替換為單一類別層級定義。這就是 @CacheConfig 發揮作用的地方。以下範例使用 @CacheConfig 設定快取的名稱

@CacheConfig("books") (1)
public class BookRepositoryImpl implements BookRepository {

	@Cacheable
	public Book findBook(ISBN isbn) {...}
}
1 使用 @CacheConfig 設定快取的名稱。

@CacheConfig 是一個類別層級註解,允許共用快取名稱、自訂 KeyGenerator、自訂 CacheManager 和自訂 CacheResolver。將此註解放在類別上不會開啟任何快取操作。

操作層級自訂始終會覆寫在 @CacheConfig 上設定的自訂。因此,這為每個快取操作提供了三個層級的自訂

  • 全域配置,例如,透過 CachingConfigurer:請參閱下一節。

  • 在類別層級,使用 @CacheConfig

  • 在操作層級。

提供者特定設定通常在 CacheManager bean 上可用,例如,在 CaffeineCacheManager 上。這些實際上也是全域的。

啟用快取註解

重要的是要注意,即使宣告快取註解也不會自動觸發它們的動作 - 就像 Spring 中的許多事物一樣,此功能必須以宣告方式啟用(這表示如果您懷疑快取是罪魁禍首,您可以透過僅移除一行配置而不是程式碼中的所有註解來停用它)。

若要啟用快取註解,請將註解 @EnableCaching 新增至您的 @Configuration 類別之一,或將 cache:annotation-driven 元素與 XML 一起使用

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableCaching
class CacheConfiguration {

	@Bean
	CacheManager cacheManager() {
		CaffeineCacheManager cacheManager = new CaffeineCacheManager();
		cacheManager.setCacheSpecification("...");
		return cacheManager;
	}
}
@Configuration
@EnableCaching
class CacheConfiguration {

	@Bean
	fun cacheManager(): CacheManager {
		return CaffeineCacheManager().apply {
			setCacheSpecification("...")
		}
	}
}
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:cache="http://www.springframework.org/schema/cache"
	   xsi:schemaLocation="
			http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
			http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd">

	<cache:annotation-driven/>

	<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
		<property name="cacheSpecification" value="..."/>
	</bean>
</beans>

cache:annotation-driven 元素和 @EnableCaching 註解都允許您指定各種選項,這些選項會影響透過 AOP 將快取行為新增至應用程式的方式。此配置與 @Transactional 的配置刻意相似。

用於處理快取註解的預設通知模式是 proxy,這僅允許透過代理攔截呼叫。同一類別內的本地呼叫無法以這種方式被攔截。對於更進階的攔截模式,請考慮切換到 aspectj 模式,並結合編譯時期或載入時期織入。
如需關於實作 CachingConfigurer 所需的進階自訂(使用 Java 配置)的更多詳細資訊,請參閱 javadoc
表 2. 快取註解設定
XML 屬性 註解屬性 預設值 描述

cache-manager

不適用(請參閱 CachingConfigurer javadoc)

cacheManager

要使用的快取管理器的名稱。預設的 CacheResolver 會在幕後使用此快取管理器(或未設定時使用 cacheManager)初始化。如需更細緻的快取解析管理,請考慮設定 'cache-resolver' 屬性。

cache-resolver

不適用(請參閱 CachingConfigurer javadoc)

使用已配置的 cacheManagerSimpleCacheResolver

要用於解析後端快取的 CacheResolver 的 bean 名稱。此屬性不是必要的,僅需要指定作為 'cache-manager' 屬性的替代方案。

key-generator

不適用(請參閱 CachingConfigurer javadoc)

SimpleKeyGenerator

要使用的自訂鍵值產生器的名稱。

error-handler

不適用(請參閱 CachingConfigurer javadoc)

SimpleCacheErrorHandler

要使用的自訂快取錯誤處理器的名稱。預設情況下,在快取相關操作期間拋出的任何例外都會拋回給用戶端。

mode

mode

proxy

預設模式(proxy)處理要使用 Spring 的 AOP 框架代理的帶註解的 bean(遵循代理語意,如先前所述,僅適用於透過代理傳入的方法呼叫)。替代模式(aspectj)反而會使用 Spring 的 AspectJ 快取 aspect 織入受影響的類別,修改目標類別位元組碼以應用於任何種類的方法呼叫。AspectJ 織入需要在類別路徑中包含 spring-aspects.jar 以及啟用載入時期織入(或編譯時期織入)。(請參閱 Spring 配置,以取得關於如何設定載入時期織入的詳細資訊。)

proxy-target-class

proxyTargetClass

false

僅適用於代理模式。控制為帶有 @Cacheable@CacheEvict 註解的類別建立何種類型的快取代理。如果 proxy-target-class 屬性設定為 true,則會建立基於類別的代理。如果 proxy-target-classfalse 或省略該屬性,則會建立標準 JDK 基於介面的代理。(請參閱 代理機制,以詳細檢視不同的代理類型。)

order

order

Ordered.LOWEST_PRECEDENCE

定義應用於帶有 @Cacheable@CacheEvict 註解的 bean 的快取通知的順序。(如需關於排序 AOP 通知的規則的更多資訊,請參閱 通知排序。)未指定排序表示 AOP 子系統決定通知的順序。

<cache:annotation-driven/> 僅在定義它的同一個應用程式環境中的 bean 上尋找 @Cacheable/@CachePut/@CacheEvict/@Caching。這表示,如果您在 DispatcherServletWebApplicationContext 中放置 <cache:annotation-driven/>,它僅檢查控制器中的 bean,而不是服務中的 bean。請參閱 MVC 區段,以取得更多資訊。
方法可見性和快取註解

當您使用代理時,您應該僅將快取註解應用於具有 public 可見性的方法。如果您確實使用這些註解來註解 protected、private 或 package-visible 方法,則不會引發錯誤,但帶註解的方法不會展現已配置的快取設定。如果您需要註解非 public 方法,請考慮使用 AspectJ(請參閱本節的其餘部分),因為它會更改位元組碼本身。

Spring 建議您僅使用 @Cache* 註解來註解具體類別(以及具體類別的方法),而不是註解介面。您當然可以在介面(或介面方法)上放置 @Cache* 註解,但這僅在您使用代理模式(mode="proxy")時才有效。如果您使用基於織入的 aspect(mode="aspectj"),則織入基礎架構不會識別介面層級宣告上的快取設定。
在代理模式(預設)下,僅攔截透過代理傳入的外部方法呼叫。這表示自我調用(實際上,目標物件內的一個方法呼叫目標物件的另一個方法)即使被調用的方法標記為 @Cacheable,也不會導致運行時的實際快取。在這種情況下,請考慮使用 aspectj 模式。此外,代理必須完全初始化才能提供預期的行為,因此您不應在初始化程式碼中依賴此功能(即 @PostConstruct)。

使用自訂註解

自訂註解和 AspectJ

此功能僅適用於基於代理的方法,但可以透過使用 AspectJ 並付出一些額外努力來啟用。

spring-aspects 模組僅為標準註解定義了一個 aspect。如果您已定義了自己的註解,您也需要為這些註解定義一個 aspect。請查看 AnnotationCacheAspect 以取得範例。

快取抽象化讓您可以使用自己的註解來識別哪個方法觸發快取填充或逐出。這作為範本機制非常方便,因為它消除了重複快取註解宣告的需求,如果指定了鍵值或條件,或者如果您的程式碼庫中不允許外部匯入 (org.springframework),則尤其有用。與其他 stereotype 註解類似,您可以將 @Cacheable@CachePut@CacheEvict@CacheConfig 用作元註解(即,可以註解其他註解的註解)。在以下範例中,我們用自己的自訂註解替換了常見的 @Cacheable 宣告

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames="books", key="#isbn")
public @interface SlowService {
}

在前面的範例中,我們定義了自己的 SlowService 註解,它本身以 @Cacheable 註解。現在我們可以替換以下程式碼

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

以下範例顯示了我們可以用來自訂註解替換前面的程式碼

@SlowService
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

即使 @SlowService 不是 Spring 註解,容器也會在運行時自動拾取其宣告並理解其含義。請注意,如先前所述,需要啟用註解驅動的行為。