Declarative Annotation-based Caching

对于缓存声明,Spring 的缓存抽象提供了一组 Java 注解:

For caching declaration, Spring’s caching abstraction provides a set of Java annotations:

  • @Cacheable: Triggers cache population.

  • @CacheEvict: Triggers cache eviction.

  • @CachePut: Updates the cache without interfering with the method execution.

  • @Caching: Regroups multiple cache operations to be applied on a method.

  • @CacheConfig: Shares some common cache-related settings at class-level.

The @Cacheable Annotation

顾名思义,你可以使用 @Cacheable 来界定可缓存的方法——即结果存储在缓存中的方法,这样在后续调用(使用相同的参数)时,会返回缓存中的值,而无需实际调用该方法。在最简单的形式中,该注解声明需要关联到带注解方法的缓存名称,如下面的示例所示:

As the name implies, you can use @Cacheable to demarcate methods that are cacheable — that is, methods for which the result is stored in the cache so that, on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually invoke the method. In its simplest form, the annotation declaration requires the name of the cache associated with the annotated method, as the following example shows:

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

在前面的代码段中,findBook 方法与名为 books 的缓存关联。每当调用该方法时,都会检查缓存,以查看调用是否已运行且无需重复运行。尽管大多数情况下只声明了一个缓存,但注解允许指定多个名称,以便使用多个缓存。在这种情况下,在调用方法之前会检查每个缓存——如果至少命中一个缓存,则返回关联的值。

In the preceding snippet, the findBook method is associated with the cache named books. Each time the method is called, the cache is checked to see whether the invocation has already been run and does not have to be repeated. While in most cases, only one cache is declared, the annotation lets multiple names be specified so that more than one cache is being used. In this case, each of the caches is checked before invoking the method — if at least one cache is hit, the associated value is returned.

即使实际上未调用缓存方法,也不包含该值的所有其他缓存也会更新。

All the other caches that do not contain the value are also updated, even though the cached method was not actually invoked.

以下示例在 findBook 方法中对多个缓存使用 @Cacheable

The following example uses @Cacheable on the findBook method with multiple caches:

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

Default Key Generation

由于缓存本质上是键值存储,因此需要将缓存方法的每次调用转换为适合缓存访问的键。缓存抽象使用基于以下算法的简单 KeyGenerator

Since caches are essentially key-value stores, each invocation of a cached method needs to be translated into a suitable key for cache access. The caching abstraction uses a simple KeyGenerator based on the following algorithm:

  • If no parameters are given, return SimpleKey.EMPTY.

  • If only one parameter is given, return that instance.

  • If more than one parameter is given, return a SimpleKey that contains all parameters.

只要参数有自然键并实现有效的 hashCode()equals() 方法,这种方法对大多数用例都适用。如果不是这种情况,则需要更改该策略。

This approach works well for most use-cases, as long as parameters have natural keys and implement valid hashCode() and equals() methods. If that is not the case, you need to change the strategy.

要提供一个不同的默认键生成器,你需要实现 org.springframework.cache.interceptor.KeyGenerator 接口。

To provide a different default key generator, you need to implement the org.springframework.cache.interceptor.KeyGenerator interface.

默认密钥生成策略已随着 Spring 4.0 的发布而更改。Spring 的早期版本使用了一种密钥生成策略,对于多个键参数,只考虑参数的 hashCode(),而不考虑 equals()。这可能会导致意外的密钥冲突(有关背景信息,请参阅https://github.com/spring-projects/spring-framework/issues/14870[spring-framework#14870])。新 `SimpleKeyGenerator`在这样的场景中使用复合密钥。

The default key generation strategy changed with the release of Spring 4.0. Earlier versions of Spring used a key generation strategy that, for multiple key parameters, considered only the hashCode() of parameters and not equals(). This could cause unexpected key collisions (see spring-framework#14870 for background). The new SimpleKeyGenerator uses a compound key for such scenarios.

如果你想继续使用以前的键策略,则可以配置已弃用的 org.springframework.cache.interceptor.DefaultKeyGenerator 类或创建一个自定义基于哈希的 KeyGenerator 实现。

If you want to keep using the previous key strategy, you can configure the deprecated org.springframework.cache.interceptor.DefaultKeyGenerator class or create a custom hash-based KeyGenerator implementation.

Custom Key Generation Declaration

由于缓存是泛型的,因此目标方法很可能会具有无法直接映射到缓存结构的各种签名。当目标方法有多个参数,其中只有部分参数适合缓存(而其余参数仅由方法逻辑使用)时,这种趋势变得明显。考虑以下示例:

Since caching is generic, the target methods are quite likely to have various signatures that cannot be readily mapped on top of the cache structure. This tends to become obvious when the target method has multiple arguments out of which only some are suitable for caching (while the rest are used only by the method logic). Consider the following example:

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

乍一看,尽管两个 boolean 参数会影响找到书的方式,但它们对缓存没有用。此外,如果其中一个很重要而另一个不重要会怎样?

At first glance, while the two boolean arguments influence the way the book is found, they are no use for the cache. Furthermore, what if only one of the two is important while the other is not?

对于此类情况,@Cacheable 注释允许您指定通过其 key 属性生成键的方式。您可以使用 SpEL 挑选感兴趣的参数(或它们的嵌套属性),执行操作,或甚至调用任意方法,而无需编写任何代码或实现任何接口。这是相对于 default generator 的推荐方法,因为随着代码库的增大,方法在签名方面往往会有很大差异。虽然默认策略可能适用于某些方法,但它很少适用于所有方法。

For such cases, the @Cacheable annotation lets you specify how the key is generated through its key attribute. You can use SpEL to pick the arguments of interest (or their nested properties), perform operations, or even invoke arbitrary methods without having to write any code or implement any interface. This is the recommended approach over the default generator, since methods tend to be quite different in signatures as the code base grows. While the default strategy might work for some methods, it rarely works for all methods.

以下示例使用各种 SpEL 声明(如果您不熟悉 SpEL,不妨自己去阅读 Spring Expression Language):

The following examples use various SpEL declarations (if you are not familiar with SpEL, do yourself a favor and read Spring Expression Language):

@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)

前面的代码段展示了选择某个参数、它的某个属性,甚至某个任意(静态)方法是多么容易。

The preceding snippets show how easy it is to select a certain argument, one of its properties, or even an arbitrary (static) method.

如果负责生成键的算法过于具体或需要共享,则可以在操作中定义自定义 keyGenerator。为此,请指定要使用的 KeyGenerator bean 实现的名称,如下面的示例所示:

If the algorithm responsible for generating the key is too specific or if it needs to be shared, you can define a custom keyGenerator on the operation. To do so, specify the name of the KeyGenerator bean implementation to use, as the following example shows:

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

keykeyGenerator 参数互斥,并且指定两者都会导致操作出现异常。

The key and keyGenerator parameters are mutually exclusive and an operation that specifies both results in an exception.

Default Cache Resolution

缓存抽象使用了一个简单的 CacheResolver,它使用已配置的 CacheManager 来检索在操作级别定义的缓存。

The caching abstraction uses a simple CacheResolver that retrieves the caches defined at the operation level by using the configured CacheManager.

要提供一个不同的默认缓存解析器,你需要实现 org.springframework.cache.interceptor.CacheResolver 接口。

To provide a different default cache resolver, you need to implement the org.springframework.cache.interceptor.CacheResolver interface.

Custom Cache Resolution

对于使用单个 CacheManager 且没有复杂缓存解析要求的应用程序,默认缓存解析非常适合。

The default cache resolution fits well for applications that work with a single CacheManager and have no complex cache resolution requirements.

对于使用多个缓存管理器的应用程序,你可以为每个操作设置要使用的 cacheManager,如下面的示例所示:

For applications that work with several cache managers, you can set the cacheManager to use for each operation, as the following example shows:

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

您还可以以类似于替换 key generation 的方式完全替换 CacheResolver。每次缓存操作都会请求解析,允许实现实际解析要使用的缓存,根据运行时参数进行解析。以下示例显示了如何指定 CacheResolver

You can also replace the CacheResolver entirely in a fashion similar to that of replacing key generation. The resolution is requested for every cache operation, letting the implementation actually resolve the caches to use based on runtime arguments. The following example shows how to specify a CacheResolver:

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

从 Spring 4.1 开始,缓存注释的 value 属性不再是必需的了,因为这个特殊信息无论注释的内容是什么,都可以由 CacheResolver 提供。

Since Spring 4.1, the value attribute of the cache annotations are no longer mandatory, since this particular information can be provided by the CacheResolver regardless of the content of the annotation.

keykeyGenerator 类似,cacheManagercacheResolver 参数是相互排斥的,一个操作指定了两个都会导致一个异常,因为一个自定义 CacheManagerCacheResolver 实现忽略了。这可能不是你所期望的。

Similarly to key and keyGenerator, the cacheManager and cacheResolver parameters are mutually exclusive, and an operation specifying both results in an exception, as a custom CacheManager is ignored by the CacheResolver implementation. This is probably not what you expect.

Synchronized Caching

在多线程环境中,某些操作可能为相同的参数并发调用(通常在启动时)。默认情况下,缓存抽象不锁定任何东西,相同的值可以计算多次,从而违背缓存的目的。

In a multi-threaded environment, certain operations might be concurrently invoked for the same argument (typically on startup). By default, the cache abstraction does not lock anything, and the same value may be computed several times, defeating the purpose of caching.

对于这些特殊情况,你可以使用 sync 属性来指示底层缓存提供者在计算值时锁定缓存条目。结果,只有一个线程在忙于计算值,而其他线程则被阻塞,直到缓存中的条目被更新。以下示例展示了如何使用 sync 属性:

For those particular cases, you can use the sync attribute to instruct the underlying cache provider to lock the cache entry while the value is being computed. As a result, only one thread is busy computing the value, while the others are blocked until the entry is updated in the cache. The following example shows how to use the sync attribute:

@Cacheable(cacheNames="foos", sync=true) 1
public Foo executeExpensiveOperation(String id) {...}
1 Using the sync attribute.

这是一个可选功能,并且您最喜爱的缓存库可能不支持它。核心框架提供的 CacheManager 实现都支持它。更多详细信息,请参见您缓存提供程序的文档。

This is an optional feature, and your favorite cache library may not support it. All CacheManager implementations provided by the core framework support it. See the documentation of your cache provider for more details.

Caching with CompletableFuture and Reactive Return Types

从 6.1 开始,缓存注释考虑 CompletableFuture 和反应式返回类型,从而自动相应调整缓存交互。

As of 6.1, cache annotations take CompletableFuture and reactive return types into account, automatically adapting the cache interaction accordingly.

对于返回 CompletableFuture 的方法,当将来产生对象时,它将被缓存,并且缓存查找缓存命中将通过 CompletableFuture 检索:

For a method returning a CompletableFuture, the object produced by that future will be cached whenever it is complete, and the cache lookup for a cache hit will be retrieved via a CompletableFuture:

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

对于返回 Reactor Mono 的方法,该 Reactive Streams 发布者发出的对象将在任何可用时被缓存,并且缓存查找缓存命中将作为 Mono(由 CompletableFuture 支持)检索:

For a method returning a Reactor Mono, the object emitted by that Reactive Streams publisher will be cached whenever it is available, and the cache lookup for a cache hit will be retrieved as a Mono (backed by a CompletableFuture):

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

对于返回 Reactor Flux 的方法,该 Reactive Streams 发布者发出的对象将被收集到 List 中,并在列表完成时被缓存,并且缓存查找缓存命中将作为 Flux(由缓存的 List 值的 CompletableFuture 支持)检索:

For a method returning a Reactor Flux, the objects emitted by that Reactive Streams publisher will be collected into a List and cached whenever that list is complete, and the cache lookup for a cache hit will be retrieved as a Flux (backed by a CompletableFuture for the cached List value):

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

这样的 CompletableFuture 和响应式适配也适用于同步缓存,在并发缓存缺失的情况下只计算一次值:

Such CompletableFuture and reactive adaptation also works for synchronized caching, computing the value only once in case of a concurrent cache miss:

@Cacheable(cacheNames="foos", sync=true) 1
public CompletableFuture<Foo> executeExpensiveOperation(String id) {...}
1 Using the sync attribute.

为了让这样的一个安排在运行时起作用,配置的缓存需要能以 CompletableFuture 为基础进行检索。Spring 提供的 ConcurrentMapCacheManager 自动适应这种检索风格,当其异步缓存模式已启用时,CaffeineCacheManager 本机支持它:在您的 CaffeineCacheManager 实例上设置 setAsyncCacheMode(true)

In order for such an arrangement to work at runtime, the configured cache needs to be capable of CompletableFuture-based retrieval. The Spring-provided ConcurrentMapCacheManager automatically adapts to that retrieval style, and CaffeineCacheManager natively supports it when its asynchronous cache mode is enabled: set setAsyncCacheMode(true) on your CaffeineCacheManager instance.

@Bean
CacheManager cacheManager() {
	CaffeineCacheManager cacheManager = new CaffeineCacheManager();
	cacheManager.setCacheSpecification(...);
	cacheManager.setAsyncCacheMode(true);
	return cacheManager;
}

最后但并非最不重要的是,请注意,基于注释的缓存不适合复杂的反应式交互,涉及组合和反压。如果您选择在特定的反应式方法上声明 @Cacheable,请考虑相当粗粒度的缓存交互的影响,它只是存储发出的对象以获取 Mono,甚至是针对 Flux 预先收集的对象列表。

Last but not least, be aware that annotation-driven caching is not appropriate for sophisticated reactive interactions involving composition and back pressure. If you choose to declare @Cacheable on specific reactive methods, consider the impact of the rather coarse-granular cache interaction which simply stores the emitted object for a Mono or even a pre-collected list of objects for a Flux.

Conditional Caching

有时,一个方法可能不适合一直被缓存(比如它可能依赖于给定参数)。缓存注释通过 condition 参数支持这样的用例,它使用一个 SpEL 表达式来评估 truefalse。如果为 true,方法会被缓存。否则,它的行为就像方法没有被缓存一样(也就是说,无论缓存中有何值或使用了何参数,该方法每次都会被调用)。比如,下列方法仅在参数 name 的长度小于 32 时缓存:

Sometimes, a method might not be suitable for caching all the time (for example, it might depend on the given arguments). The cache annotations support such use cases through the condition parameter, which takes a SpEL expression that is evaluated to either true or false. If true, the method is cached. If not, it behaves as if the method is not cached (that is, the method is invoked every time no matter what values are in the cache or what arguments are used). For example, the following method is cached only if the argument name has a length shorter than 32:

@Cacheable(cacheNames="book", condition="#name.length() < 32") 1
public Book findBook(String name)
1 Setting a condition on @Cacheable.

除了 condition 参数,你可以使用 unless 参数来否决将值添加到缓存中。与 condition 不同,unless 表达式是在调用方法之后才评估的。为了扩展前面的示例,也许我们只想缓存平装书,如下面的示例所示:

In addition to the condition parameter, you can use the unless parameter to veto the adding of a value to the cache. Unlike condition, unless expressions are evaluated after the method has been invoked. To expand on the previous example, perhaps we only want to cache paperback books, as the following example does:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") 1
public Book findBook(String name)
1 Using the unless attribute to block hardbacks.

缓存抽象支持 java.util.Optional 返回类型。如果存在 Optional 值,它将被存储在关联的缓存中。如果不存在 Optional 值,null 将被存储在关联的缓存中。#result 始终是指业务实体,永远不会是指受支持的包装器,因此前面的示例可以重写如下:

The cache abstraction supports java.util.Optional return types. If an Optional value is present, it will be stored in the associated cache. If an Optional value is not present, null will be stored in the associated cache. #result always refers to the business entity and never a supported wrapper, so the previous example can be rewritten as follows:

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

请注意,#result`仍然是指 `Book,而不是 Optional<Book>。因为它是`null`,所以我们使用 SpEL 的 safe navigation operator

Note that #result still refers to Book and not Optional<Book>. Since it might be null, we use SpEL’s safe navigation operator.

Available Caching SpEL Evaluation Context

每个 SpEL 表达式均针对专门的 context 进行评估。除了内置参数外,框架还提供专门的与缓存相关的元数据,例如参数名称。下表描述了可用的项目,以便你可以将它们用于键和条件计算:

Each SpEL expression evaluates against a dedicated context. In addition to the built-in parameters, the framework provides dedicated caching-related metadata, such as the argument names. The following table describes the items made available to the context so that you can use them for key and conditional computations:

Table 1. Cache SpEL available metadata
Name Location Description Example

methodName

Root object

The name of the method being invoked

#root.methodName

method

Root object

The method being invoked

#root.method.name

target

Root object

The target object being invoked

#root.target

targetClass

Root object

The class of the target being invoked

#root.targetClass

args

Root object

The arguments (as array) used for invoking the target

#root.args[0]

caches

Root object

Collection of caches against which the current method is run

#root.caches[0].name

Argument name

Evaluation context

Name of any of the method arguments. If the names are not available (perhaps due to having no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0).

#iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias).

result

Evaluation context

The result of the method call (the value to be cached). Only available in unless expressions, cache put expressions (to compute the key), or cache evict expressions (when beforeInvocation is false). For supported wrappers (such as Optional), #result refers to the actual object, not the wrapper.

#result

The @CachePut Annotation

当需要更新缓存而不对方法执行产生干扰时,你可以使用 @CachePut 注释。也就是说,该方法总是会被调用,并且它的结果会被放入缓存(根据 @CachePut 选项)。它支持与 @Cacheable 相同的选项,并且应该用于缓存填充,而不是方法流程优化。以下示例使用了 @CachePut 注释:

When the cache needs to be updated without interfering with the method execution, you can use the @CachePut annotation. That is, the method is always invoked and its result is placed into the cache (according to the @CachePut options). It supports the same options as @Cacheable and should be used for cache population rather than method flow optimization. The following example uses the @CachePut annotation:

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)

总体上强烈不建议对同一个方法使用 @CachePut@Cacheable 注释,因为它们具有不同的行为。而后者通过使用缓存来跳过方法调用,前者强制进行调用以运行缓存更新。这会导致意外的行为,并且,除了特定特例(如注释具有互相排除的条件),这样的声明应该避免。另请注意,这样的条件不应该依赖于结果对象(即,#result 变量),因为这些条件在最开始就已通过验证来确认排除。

Using @CachePut and @Cacheable annotations on the same method is generally strongly discouraged because they have different behaviors. While the latter causes the method invocation to be skipped by using the cache, the former forces the invocation in order to run a cache update. This leads to unexpected behavior and, with the exception of specific corner-cases (such as annotations having conditions that exclude them from each other), such declarations should be avoided. Note also that such conditions should not rely on the result object (that is, the #result variable), as these are validated up-front to confirm the exclusion.

从 6.1 开始,@CachePut 会考虑 CompletableFuture 和反应式返回类型,当产生的对象可用时执行 put 操作。

As of 6.1, @CachePut takes CompletableFuture and reactive return types into account, performing the put operation whenever the produced object is available.

The @CacheEvict Annotation

缓存抽象不仅允许填充缓存存储,还允许驱逐。此过程可用于从缓存中删除陈旧或未使用的数据。与 @Cacheable 相反,@CacheEvict 界定执行缓存驱逐的方法(即触发从缓存中删除数据的动作的方法)。与它类似,@CacheEvict 要求指定一个或多个受操作影响的缓存,允许指定一个自定义缓存和键解析器或条件,并具有一个额外的参数(allEntries),该参数指示是否需要执行整个缓存驱逐,而不是仅仅执行基于键的条目驱逐。以下示例从 books 缓存驱逐所有条目:

The cache abstraction allows not just population of a cache store but also eviction. This process is useful for removing stale or unused data from the cache. As opposed to @Cacheable, @CacheEvict demarcates methods that perform cache eviction (that is, methods that act as triggers for removing data from the cache). Similarly to its sibling, @CacheEvict requires specifying one or more caches that are affected by the action, allows a custom cache and key resolution or a condition to be specified, and features an extra parameter (allEntries) that indicates whether a cache-wide eviction needs to be performed rather than just an entry eviction (based on the key). The following example evicts all entries from the books cache:

@CacheEvict(cacheNames="books", allEntries=true) 1
public void loadBooks(InputStream batch)
1 Using the allEntries attribute to evict all entries from the cache.

当整个缓存区域需要清除时,此选项非常有用。与逐个驱逐条目(由于效率低下,这将花费较长时间)不同,所有条目在一次操作中被移除,正如前面的示例所示。请注意,框架忽略此场景中指定的任何键,因为它不适用(驱逐整个缓存,而不仅仅是一个条目)。

This option comes in handy when an entire cache region needs to be cleared out. Rather than evicting each entry (which would take a long time, since it is inefficient), all the entries are removed in one operation, as the preceding example shows. Note that the framework ignores any key specified in this scenario as it does not apply (the entire cache is evicted, not only one entry).

您还可以使用 beforeInvocation 属性指示驱逐是否应在方法被调用后(默认)或之前发生。前者提供与其他注释相同的语义:方法成功完成后,对缓存执行操作(在本例中为驱逐)。如果方法未运行(因为它可能是缓存的)或抛出异常,则不会发生驱逐。后者(beforeInvocation=true)会导致在调用方法之前始终发生驱逐。这在驱逐不需要与方法输出关联的情况下很有用。

You can also indicate whether the eviction should occur after (the default) or before the method is invoked by using the beforeInvocation attribute. The former provides the same semantics as the rest of the annotations: Once the method completes successfully, an action (in this case, eviction) on the cache is run. If the method does not run (as it might be cached) or an exception is thrown, the eviction does not occur. The latter (beforeInvocation=true) causes the eviction to always occur before the method is invoked. This is useful in cases where the eviction does not need to be tied to the method outcome.

请注意,@CacheEvict 可与 void 方法一起使用——由于方法充当触发器,因此返回值被忽略(因为它们不与缓存交互)。而 @Cacheable 则不同,它向缓存添加数据或更新缓存中的数据,因此需要一个结果。

Note that void methods can be used with @CacheEvict - as the methods act as a trigger, the return values are ignored (as they do not interact with the cache). This is not the case with @Cacheable which adds data to the cache or updates data in the cache and, thus, requires a result.

从 6.1 开始,@CacheEvict 考虑 CompletableFuture 和响应式返回类型,只要处理完成,就会执行驱逐操作。

As of 6.1, @CacheEvict takes CompletableFuture and reactive return types into account, performing an after-invocation evict operation whenever processing has completed.

The @Caching Annotation

有时,需要指定多个相同类型的注释(例如 @CacheEvict@CachePut)——例如,因为条件或键表达式在不同缓存之间不同。@Caching` 允许在同一方法上使用多个嵌套的 @Cacheable@CachePut@CacheEvict 注释。以下示例使用两个 @CacheEvict 注释:

Sometimes, multiple annotations of the same type (such as @CacheEvict or @CachePut) need to be specified — for example, because the condition or the key expression is different between different caches. @Caching lets multiple nested @Cacheable, @CachePut, and @CacheEvict annotations be used on the same method. The following example uses two @CacheEvict annotations:

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

The @CacheConfig Annotation

到目前为止,我们已经看到,缓存操作提供许多自定义选项,您可以为每个操作设置这些选项。但是,如果一些自定义选项适用于类的所有操作,则配置起来可能会很繁琐。例如,可以将为类的每个缓存操作指定要使用的缓存名称替换为单个类级定义。这就是 @CacheConfig 发挥作用的地方。以下示例使用 @CacheConfig 设置缓存的名称:

So far, we have seen that caching operations offer many customization options and that you can set these options for each operation. However, some of the customization options can be tedious to configure if they apply to all operations of the class. For instance, specifying the name of the cache to use for every cache operation of the class can be replaced by a single class-level definition. This is where @CacheConfig comes into play. The following examples uses @CacheConfig to set the name of the cache:

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

	@Cacheable
	public Book findBook(ISBN isbn) {...}
}
1 Using @CacheConfig to set the name of the cache.

@CacheConfig 是一个类级注释,允许共享缓存名称、自定义 KeyGenerator、自定义 CacheManager 和自定义 CacheResolver。在类上放置此注释不会启用任何缓存操作。

@CacheConfig is a class-level annotation that allows sharing the cache names, the custom KeyGenerator, the custom CacheManager, and the custom CacheResolver. Placing this annotation on the class does not turn on any caching operation.

操作级自定义始终会覆盖 @CacheConfig 上设置的自定义项。因此,这为每个缓存操作提供了三个层级的自定义:

An operation-level customization always overrides a customization set on @CacheConfig. Therefore, this gives three levels of customizations for each cache operation:

  • Globally configured, e.g. through CachingConfigurer: see next section.

  • At the class level, using @CacheConfig.

  • At the operation level.

通常在 CacheManager Bean 上提供特定于提供者的设置,例如在 CaffeineCacheManager 上。这些设置实际上也是全局的。

Provider-specific settings are typically available on the CacheManager bean, e.g. on CaffeineCacheManager. These are effectively also global.

Enabling Caching Annotations

值得注意的是,即使声明缓存注释不会自动触发其操作——就像 Spring 中的许多内容一样,该功能必须通过声明方式启用(这意味着如果您怀疑是缓存的过错,您可以通过删除代码中的所有注释而不是仅仅删除一个配置行来禁用它)。

It is important to note that even though declaring the cache annotations does not automatically trigger their actions - like many things in Spring, the feature has to be declaratively enabled (which means if you ever suspect caching is to blame, you can disable it by removing only one configuration line rather than all the annotations in your code).

若要启用缓存注释,请在 @Configuration 类之一中添加注释 @EnableCaching,或与 XML 一起使用元素 cache:annotation-driven

To enable caching annotations add the annotation @EnableCaching to one of your @Configuration classes or use the cache:annotation-driven element with XML:

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableCaching
public 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 的配置故意相似。

Both the cache:annotation-driven element and the @EnableCaching annotation let you specify various options that influence the way the caching behavior is added to the application through AOP. The configuration is intentionally similar with that of @Transactional.

处理缓存注释的默认建议模式为 proxy,它只允许通过代理拦截调用。同一类中的本地调用不能使用这种方式进行拦截。对于一个更高级的拦截模式,请考虑将 aspectj 模式与编译时或加载时编织结合使用。

The default advice mode for processing caching annotations is proxy, which allows for interception of calls through the proxy only. Local calls within the same class cannot get intercepted that way. For a more advanced mode of interception, consider switching to aspectj mode in combination with compile-time or load-time weaving.

有关实现 CachingConfigurer 所需的高级自定义项(使用 Java 配置)的更多详细信息,请参阅 javadoc

For more detail about advanced customizations (using Java configuration) that are required to implement CachingConfigurer, see the javadoc.

Table 2. Cache annotation settings
XML Attribute Annotation Attribute Default Description

cache-manager

N/A (see the CachingConfigurer javadoc)

cacheManager

The name of the cache manager to use. A default CacheResolver is initialized behind the scenes with this cache manager (or cacheManager if not set). For more fine-grained management of the cache resolution, consider setting the 'cache-resolver' attribute.

cache-resolver

N/A (see the CachingConfigurer javadoc)

A SimpleCacheResolver using the configured cacheManager.

The bean name of the CacheResolver that is to be used to resolve the backing caches. This attribute is not required and needs to be specified only as an alternative to the 'cache-manager' attribute.

key-generator

N/A (see the CachingConfigurer javadoc)

SimpleKeyGenerator

Name of the custom key generator to use.

error-handler

N/A (see the CachingConfigurer javadoc)

SimpleCacheErrorHandler

The name of the custom cache error handler to use. By default, any exception thrown during a cache related operation is thrown back at the client.

mode

mode

proxy

The default mode (proxy) processes annotated beans to be proxied by using Spring’s AOP framework (following proxy semantics, as discussed earlier, applying to method calls coming in through the proxy only). The alternative mode (aspectj) instead weaves the affected classes with Spring’s AspectJ caching aspect, modifying the target class byte code to apply to any kind of method call. AspectJ weaving requires spring-aspects.jar in the classpath as well as load-time weaving (or compile-time weaving) enabled. (See Spring configuration for details on how to set up load-time weaving.)

proxy-target-class

proxyTargetClass

false

Applies to proxy mode only. Controls what type of caching proxies are created for classes annotated with the @Cacheable or @CacheEvict annotations. If the proxy-target-class attribute is set to true, class-based proxies are created. If proxy-target-class is false or if the attribute is omitted, standard JDK interface-based proxies are created. (See Proxying Mechanisms for a detailed examination of the different proxy types.)

order

order

Ordered.LOWEST_PRECEDENCE

Defines the order of the cache advice that is applied to beans annotated with @Cacheable or @CacheEvict. (For more information about the rules related to ordering AOP advice, see Advice Ordering.) No specified ordering means that the AOP subsystem determines the order of the advice.

<cache:annotation-driven/> 仅在定义它的同一应用程序上下文中查找 @Cacheable/@CachePut/@CacheEvict/@Caching。这意味着,如果您将 <cache:annotation-driven/> 放入 WebApplicationContext 中以用于 DispatcherServlet,它只会检查控制器中的 Bean,而不会检查服务中的 Bean。有关更多信息,请参阅 the MVC section

<cache:annotation-driven/> looks for @Cacheable/@CachePut/@CacheEvict/@Caching only on beans in the same application context in which it is defined. This means that, if you put <cache:annotation-driven/> in a WebApplicationContext for a DispatcherServlet, it checks for beans only in your controllers, not your services. See the MVC section for more information.

Method visibility and cache annotations

当您使用代理时,您应该仅对具有公共可见性的方法应用缓存注释。如果您使用这些注释对受保护的、私有的或包可见的方法进行注释,则不会引发错误,但带注释的方法不会表现出已配置的缓存设置。如果您需要注释非公共方法,请考虑使用 AspectJ(请参阅本节的其余部分),因为它更改了字节码本身。

When you use proxies, you should apply the cache annotations only to methods with public visibility. If you do annotate protected, private, or package-visible methods with these annotations, no error is raised, but the annotated method does not exhibit the configured caching settings. Consider using AspectJ (see the rest of this section) if you need to annotate non-public methods, as it changes the bytecode itself.

Spring 建议您仅使用 @Cache* 注释具体类(和具体类的类方法),而不是注释接口。您当然可以将 @Cache* 注释放在接口(或接口方法)上,但这仅在您使用代理模式 (mode="proxy") 时才有效。如果您使用基于编织的方面 (mode="aspectj"),则编织基础设施无法在接口级别声明上识别缓存设置。

Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Cache* annotations, as opposed to annotating interfaces. You certainly can place an @Cache* annotation on an interface (or an interface method), but this works only if you use the proxy mode (mode="proxy"). If you use the weaving-based aspect (mode="aspectj"), the caching settings are not recognized on interface-level declarations by the weaving infrastructure.

在代理模式(默认模式)中,仅通过代理传入的外部方法调用才被拦截。这意味着,即使被调用方法用 @Cacheable 标记,自调用(实际上,是目标对象中的一个方法调用 target 对象的另一个方法)在运行时也不会导致实际缓存。请考虑在此情况下使用 aspectj 模式。另请注意,代理必须被完全初始化才能提供预期行为,因此您不应该在自己的初始化代码(即,@PostConstruct)中依赖此功能。

In proxy mode (the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object that calls another method of the target object) does not lead to actual caching at runtime even if the invoked method is marked with @Cacheable. Consider using the aspectj mode in this case. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, @PostConstruct).

Using Custom Annotations

Custom annotation and AspectJ

此功能仅适用于基于代理的方法,但通过使用 AspectJ 付出一些额外的努力可以启用它。

This feature works only with the proxy-based approach but can be enabled with a bit of extra effort by using AspectJ.

spring-aspects 模块仅为标准注释定义了一个方面。如果您已经定义了自己的注释,则还需要为它们定义一个方面。请查看 AnnotationCacheAspect 了解示例。

The spring-aspects module defines an aspect for the standard annotations only. If you have defined your own annotations, you also need to define an aspect for those. Check AnnotationCacheAspect for an example.

缓存抽象允许您使用自己的注释来标识触发缓存填充或驱逐的方法。这非常适合用作模板机制,因为它消除了复制缓存注释声明的需要,如果指定了键或条件或在您的代码库中不允许外部导入 (org.springframework),这尤其有用。与 stereotype 注释的其他部分类似,您可以将 @Cacheable@CachePut@CacheEvict@CacheConfig 用作 meta-annotations(即,可以注释其他注释的注释)。在以下示例中,我们用自己的自定义注释替换一个常见的 @Cacheable 声明:

The caching abstraction lets you use your own annotations to identify what method triggers cache population or eviction. This is quite handy as a template mechanism, as it eliminates the need to duplicate cache annotation declarations, which is especially useful if the key or condition are specified or if the foreign imports (org.springframework) are not allowed in your code base. Similarly to the rest of the stereotype annotations, you can use @Cacheable, @CachePut, @CacheEvict, and @CacheConfig as meta-annotations (that is, annotations that can annotate other annotations). In the following example, we replace a common @Cacheable declaration with our own custom annotation:

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

在前面的示例中,我们已经定义了我们自己的 SlowService 注释,它本身带 @Cacheable 注释。现在,我们可以替换以下代码:

In the preceding example, we have defined our own SlowService annotation, which itself is annotated with @Cacheable. Now we can replace the following code:

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

以下示例显示了我们可以用它来替换前一个代码的自定义注释:

The following example shows the custom annotation with which we can replace the preceding code:

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

即使 @SlowService 不是一个 Spring 注释,容器也会在运行时自动提取其声明,并理解其含义。请注意,如 earlier 中所述,需要启用基于注释的行为。

Even though @SlowService is not a Spring annotation, the container automatically picks up its declaration at runtime and understands its meaning. Note that, as mentioned earlier, annotation-driven behavior needs to be enabled.