宣言的なアノテーションベースのキャッシュ
キャッシュ宣言のために、Spring のキャッシュ抽象化は Java アノテーションのセットを提供します:
@Cacheable
: キャッシュ作成をトリガーします。@CacheEvict
: キャッシュエビクションをトリガーします。@CachePut
: メソッドの実行を妨げることなくキャッシュを更新します。@Caching
: メソッドに適用される複数のキャッシュ操作を再グループ化します。@CacheConfig
: クラスレベルでいくつかの一般的なキャッシュ関連の設定を共有します。
@Cacheable
アノテーション
名前が示すように、@Cacheable
を使用して、キャッシュ可能なメソッド、つまり、結果がキャッシュに保存されるメソッドを区別できます。これにより、後続の呼び出し(同じ引数を使用)で、キャッシュ内の値は返されません。実際にメソッドを呼び出す必要があります。次の例に示すように、最も簡単な形式では、アノテーション宣言にはアノテーション付きメソッドに関連付けられたキャッシュの名前が必要です。
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
上記のスニペットでは、findBook
メソッドは books
という名前のキャッシュに関連付けられています。メソッドが呼び出されるたびに、キャッシュがチェックされ、呼び出しがすでに実行されており、繰り返す必要がないかどうかが確認されます。ほとんどの場合、宣言されるキャッシュは 1 つだけですが、アノテーションでは複数の名前を指定できるため、複数のキャッシュが使用されます。この場合、メソッドを呼び出す前に各キャッシュがチェックされます。少なくとも 1 つのキャッシュがヒットした場合、関連する値が返されます。
キャッシュされたメソッドが実際に呼び出されなかった場合でも、値を含まない他のすべてのキャッシュも更新されます。 |
次の例では、複数のキャッシュを使用する findBook
メソッドで @Cacheable
を使用しています。
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
デフォルトのキー生成
キャッシュは基本的にキーと値のストアであるため、キャッシュされたメソッドの各呼び出しは、キャッシュアクセスに適したキーに変換する必要があります。キャッシングの抽象化では、次のアルゴリズムに基づいた単純な KeyGenerator
を使用します。
パラメーターが指定されていない場合は、
SimpleKey.EMPTY
を返します。パラメーターが 1 つしか指定されていない場合は、そのインスタンスを返します。
複数のパラメーターが指定されている場合は、すべてのパラメーターを含む
SimpleKey
を返します。
パラメーターが自然キーを持ち、有効な hashCode()
および equals()
メソッドを実装している限り、このアプローチはほとんどのユースケースでうまく機能します。そうでない場合は、戦略を変更する必要があります。
別のデフォルトキージェネレーターを提供するには、org.springframework.cache.interceptor.KeyGenerator
インターフェースを実装する必要があります。
デフォルトのキー生成戦略は、Spring 4.0 のリリースとともに変更されました。Spring の以前のバージョンでは、複数のキーパラメーターについて、 以前のキー戦略を引き続き使用する場合は、非推奨の |
カスタムキー生成宣言
キャッシングは汎用的であるため、ターゲットメソッドには、キャッシュ構造の上に簡単にマッピングできないさまざまなシグネチャーが含まれている可能性が非常に高くなります。これは、ターゲットメソッドに複数の引数があり、そのうちいくつかのみがキャッシュに適している場合に明らかになります(残りはメソッドロジックによってのみ使用されます)。次の例を考えてみましょう。
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
一見すると、2 つの boolean
引数は本の検索方法に影響を与えますが、キャッシュには使用できません。さらに、2 つのうち 1 つだけが重要で、もう 1 つは重要でない場合はどうでしょうか。
そのような場合、@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)
上記のスニペットは、特定の引数、そのプロパティの 1 つ、任意の(静的)メソッドを選択するのがいかに簡単かを示しています。
キーの生成を担当するアルゴリズムが具体的すぎる場合、または共有する必要がある場合は、操作でカスタム keyGenerator
を定義できます。これを行うには、次の例に示すように、使用する KeyGenerator
Bean 実装の名前を指定します。
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
key パラメーターと keyGenerator パラメーターは相互に排他的であり、両方を指定する操作は例外になります。 |
デフォルトのキャッシュ解決
キャッシング抽象化は、構成された CacheManager
を使用して、操作レベルで定義されたキャッシュを取得する単純な CacheResolver
を使用します。
別のデフォルトのキャッシュリゾルバーを提供するには、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 以降、キャッシュのアノテーションの
|
同期キャッシング
マルチスレッド環境では、特定の操作が同じ引数に対して(通常は起動時に)同時に呼び出される場合があります。デフォルトでは、キャッシュの抽象化は何もロックせず、同じ値が数回計算され、キャッシュの目的を無効にします。
これらの特定のケースでは、sync
属性を使用して、基になるキャッシュプロバイダーに、値の計算中にキャッシュエントリをロックするように指示できます。その結果、値の計算でビジー状態にあるスレッドは 1 つだけですが、他のスレッドはキャッシュ内のエントリが更新されるまでブロックされます。次の例は、sync
属性の使用方法を示しています。
@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 | sync 属性を使用します。 |
これはオプションの機能であり、お気に入りのキャッシュライブラリがサポートしていない場合があります。コアフレームワークによって提供されるすべての CacheManager 実装は、それをサポートします。詳細については、キャッシュプロバイダーのドキュメントを参照してください。 |
CompletableFuture およびリアクティブ戻り型を使用したキャッシュ
6.1 以降、キャッシュアノテーションは CompletableFuture
とリアクティブな戻り値の型を考慮し、それに応じてキャッシュの対話を自動的に調整します。
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
とリアクティブ適応は同期キャッシュにも機能し、同時キャッシュミスの場合に値を 1 回だけ計算します。
@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;
}
最後に重要なことですが、アノテーション駆動型のキャッシュは、合成やバックプレッシャーを伴う高度なリアクティブな相互作用には適していないことに注意してください。特定のリアクティブメソッドで @Cacheable
を宣言することを選択した場合は、Mono
の場合は発行されたオブジェクト、または Flux
の場合は事前に収集されたオブジェクトのリストを単に格納する、かなり粒度の粗いキャッシュインタラクションの影響を考慮してください。
条件付きキャッシュ
時々、メソッドは常にキャッシュするのに適していない場合があります(たとえば、指定された引数に依存する場合があります)。キャッシュアノテーションは、true
または false
のいずれかに評価される SpEL
式を取る condition
パラメーターを介して、このようなユースケースをサポートします。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
は、Optional<Book>
ではなく Book
を参照していることに注意してください。null
である可能性があるため、SpEL のセーフナビゲーション演算子を使用します。
利用可能なキャッシング SpEL 評価コンテキスト
各 SpEL
式は、専用の context
に対して評価されます。フレームワークは、組み込みパラメーターに加えて、引数名などの専用のキャッシュ関連メタデータを提供します。次の表は、キーおよび条件付き計算に使用できるように、コンテキストで使用できるようになっているアイテムを示しています。
名前 | ロケーション | 説明 | サンプル |
---|---|---|---|
| ルートオブジェクト | 呼び出されるメソッドの名前 |
|
| ルートオブジェクト | 呼び出されるメソッド |
|
| ルートオブジェクト | 呼び出されるターゲットオブジェクト |
|
| ルートオブジェクト | 呼び出されるターゲットのクラス |
|
| ルートオブジェクト | ターゲットを呼び出すために使用される引数(オブジェクト配列) |
|
| ルートオブジェクト | 現在のメソッドが実行されるキャッシュのコレクション |
|
引数名 | 評価コンテキスト | 特定のメソッド引数の名前。名前が使用できない場合 (たとえば、コードが |
|
| 評価コンテキスト | メソッド呼び出しの結果(キャッシュされる値)。 |
|
@CachePut
アノテーション
メソッドの実行を妨げずにキャッシュを更新する必要がある場合は、@CachePut
アノテーションを使用できます。つまり、メソッドが常に呼び出され、その結果が(@CachePut
オプションに従って)キャッシュに配置されます。@Cacheable
と同じオプションをサポートし、メソッドフローの最適化ではなく、キャッシュの生成に使用する必要があります。次の例では、@CachePut
アノテーションを使用しています。
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
同じメソッドで @CachePut アノテーションと @Cacheable アノテーションを使用することは、動作が異なるため、一般的にはお勧めしません。後者では、キャッシュを使用してメソッドの呼び出しがスキップされますが、前者では、キャッシュの更新を実行するために呼び出しが強制されます。これは予期しない動作を引き起こし、特定のコーナーケース(相互に除外する条件を持つアノテーションなど)を除いて、そのような宣言は回避する必要があります。これらの条件は除外を確認するために事前に検証されるため、そのような条件は結果オブジェクト(つまり、#result 変数)に依存すべきではないことにも注意してください。 |
6.1 以降、@CachePut
は CompletableFuture
とリアクティブな戻り値の型を考慮し、生成されたオブジェクトが利用可能になるたびに put 操作を実行します。
@CacheEvict
アノテーション
キャッシュの抽象化により、キャッシュストアの取り込みだけでなく、追い出しも可能になります。このプロセスは、古いデータや未使用のデータをキャッシュから削除できます。@Cacheable
とは対照的に、@CacheEvict
はキャッシュエビクションを実行するメソッド(つまり、キャッシュからデータを削除するトリガーとして機能するメソッド)を区別します。兄弟と同様に、@CacheEvict
はアクションの影響を受ける 1 つ以上のキャッシュを指定する必要があり、カスタムキャッシュとキー解決または条件を指定でき、キャッシュ全体のエビクションが必要かどうかを示す追加パラメーター(allEntries
)を備えています。エントリの追い出し(キーに基づく)ではなく、実行されます。次の例では、books
キャッシュからすべてのエントリを削除します。
@CacheEvict(cacheNames="books", allEntries=true) (1)
public void loadBooks(InputStream batch)
1 | allEntries 属性を使用して、キャッシュからすべてのエントリを削除します。 |
このオプションは、キャッシュ領域全体をクリアする必要がある場合に便利です。前の例に示すように、各エントリを削除するのではなく(非効率的であるため時間がかかります)、すべてのエントリが 1 回の操作で削除されます。フレームワークは適用されないため、このシナリオで指定されたキーを無視します(1 つのエントリだけでなく、キャッシュ全体が削除されます)。
beforeInvocation
属性を使用して、メソッドが呼び出される前(デフォルト)または呼び出される前にエビクションを実行するかどうかを指定することもできます。前者は、残りのアノテーションと同じセマンティクスを提供します。メソッドが正常に完了すると、キャッシュに対するアクション(この場合はエビクション)が実行されます。メソッドが実行されない(キャッシュされる可能性がある)か、例外がスローされた場合、エビクションは発生しません。後者(beforeInvocation=true
)では、メソッドが呼び出される前に常にエビクションが発生します。これは、エビクションをメソッドの結果に関連付ける必要がない場合に役立ちます。
void
メソッドは @CacheEvict
で使用できることに注意してください。メソッドはトリガーとして機能するため、戻り値は無視されます(キャッシュと相互作用しないため)。これは、キャッシュにデータを追加したり、キャッシュ内のデータを更新したりする @Cacheable
の場合とは異なり、結果を必要とします。
6.1 以降、@CacheEvict
は CompletableFuture
とリアクティブな戻り値の型を考慮し、処理が完了するたびに呼び出し後の削除操作を実行します。
@Caching
アノテーション
同じ型の複数のアノテーション(@CacheEvict
や @CachePut
など)を指定する必要がある場合があります。たとえば、条件やキー式がキャッシュごとに異なるためです。@Caching
を使用すると、ネストされた複数の @Cacheable
、@CachePut
、@CacheEvict
アノテーションを同じメソッドで使用できます。次の例では、2 つの @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
のカスタマイズセットをオーバーライドします。これにより、キャッシュ操作ごとに 3 つのレベルのカスタマイズが可能になります。
グローバルに構成されたもの、例:
CachingConfigurer
まで: 次のセクションを参照してください。クラスレベルで、
@CacheConfig
を使用します。操作レベル。
プロバイダー固有の設定は、通常、CacheManager Bean(CaffeineCacheManager など)で利用できます。これらも実質的にグローバルです。 |
キャッシングアノテーションの有効化
キャッシュアノテーションを宣言してもアクションは自動的にトリガーされませんが、Spring の多くの機能と同様に、この機能は宣言的に有効にする必要があります(つまり、キャッシュが原因であると思われる場合は、削除して無効にすることができます)コード内のすべてのアノテーションではなく、1 行の構成行のみ)。
アノテーションのキャッシュを有効にするには、@EnableCaching
を @Configuration
クラスの 1 つに追加します。
@Configuration
@EnableCaching
class AppConfig {
@Bean
CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCacheSpecification(...);
return cacheManager;
}
}
または、XML 構成の場合、cache:annotation-driven
要素を使用できます。
<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 を参照してください。 |
XML 属性 | アノテーション属性 | デフォルト | 説明 |
---|---|---|---|
| なし ( |
| 使用するキャッシュマネージャーの名前。デフォルトの |
| なし ( | 構成された | バッキングキャッシュの解決に使用される CacheResolver の Bean 名。この属性は必須ではなく、'cache-manager' 属性の代替としてのみ指定する必要があります。 |
| なし ( |
| 使用するカスタムキージェネレーターの名前。 |
| なし ( |
| 使用するカスタムキャッシュエラーハンドラーの名前。デフォルトでは、キャッシュ関連の操作中にスローされた例外はすべてクライアントでスローされます。 |
|
|
| デフォルトモード( |
|
|
| プロキシモードのみに適用されます。 |
|
| Ordered.LOWEST_PRECEDENCE |
|
<cache:annotation-driven/> は、それが定義されているのと同じアプリケーションコンテキスト内の Bean 上でのみ @Cacheable/@CachePut/@CacheEvict/@Caching を検索します。これは、DispatcherServlet の WebApplicationContext に <cache:annotation-driven/> を置くと、サービスではなくコントローラー内の Bean のみがチェックされることを意味します。詳細については、 "MVC" セクションを参照してください。 |
Spring は、インターフェースにアノテーションを付けるのではなく、@Cache* アノテーションで具象クラス(および具象クラスのメソッド)にのみアノテーションを付けることをお勧めします。確かに、@Cache* アノテーションをインターフェース(またはインターフェースメソッド)に配置できますが、これはプロキシモード(mode="proxy" )を使用する場合にのみ機能します。ウィービングベースのアスペクト(mode="aspectj" )を使用する場合、キャッシング設定は、ウィービングインフラストラクチャによるインターフェースレベルの宣言では認識されません。 |
プロキシモード(デフォルト)では、プロキシを介して受信する外部メソッド呼び出しのみがインターセプトされます。これは、呼び出されたメソッドが @Cacheable でマークされている場合でも、自己呼び出し(実際には、ターゲットオブジェクトの別のメソッドを呼び出すターゲットオブジェクト内のメソッド)が実行時に実際のキャッシュにつながらないことを意味します。この場合、aspectj モードの使用を検討してください。また、期待される動作を提供するためにプロキシを完全に初期化する必要があるため、初期化コード(つまり @PostConstruct )でこの機能に依存しないでください。 |
カスタムアノテーションの使用
キャッシングの抽象化により、独自のアノテーションを使用して、キャッシュの作成またはエビクションをトリガーするメソッドを識別できます。これは、キャッシュアノテーション宣言を複製する必要がないため、テンプレートメカニズムとして非常に便利です。これは、キーまたは条件が指定されている場合、またはコードベースで外部インポート(org.springframework
)が許可されていない場合に特に役立ちます。残りのステレオタイプアノテーションと同様に、@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 アノテーションではありませんが、コンテナーは実行時に自動的に宣言を取得し、その意味を理解します。前述のように、アノテーション駆動型の動作を有効にする必要があることに注意してください。