对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
基于声明性注释的缓存
对于缓存声明, Spring 的缓存抽象提供了一组 Java 注释:
-
@Cacheable
:触发缓存填充。 -
@CacheEvict
:触发缓存逐出。 -
@CachePut
:在不干扰方法执行的情况下更新缓存。 -
@Caching
:重新组合要应用于方法的多个缓存作。 -
@CacheConfig
:在类级别共享一些常见的缓存相关设置。
这@Cacheable
注解
顾名思义,您可以使用@Cacheable
划分可缓存的方法 — 即,其结果存储在缓存中的方法,以便在后续的
invocations(具有相同的参数)时,返回缓存中的值时,不会返回
必须实际调用该方法。在最简单的形式中,注解声明
需要与 annotated 方法关联的缓存的名称,如下所示
示例显示:
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
在前面的代码段中,findBook
方法与名为books
.
每次调用该方法时,都会检查缓存以查看调用是否具有
已运行,无需重复。而在大多数情况下,只有一个
cache 时,注解允许指定多个名称,以便多个
cache 正在使用。在这种情况下,在调用
method — 如果至少命中了一个缓存,则返回关联的值。
不包含该值的所有其他缓存也会更新,即使 缓存的方法实际上并未被调用。 |
以下示例使用@Cacheable
在findBook
方法中:
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
默认密钥生成
由于缓存本质上是键值存储,因此每次调用缓存的方法
需要翻译成适合缓存访问的 key。缓存抽象
使用简单的KeyGenerator
基于以下算法:
-
如果未给出参数,则返回
SimpleKey.EMPTY
. -
如果只给出一个参数,则返回该实例。
-
如果给出了多个参数,则返回一个
SimpleKey
,其中包含所有参数。
这种方法适用于大多数用例,只要参数具有自然键即可
并实施 validhashCode()
和equals()
方法。如果不是这种情况,
你需要改变策略。
要提供不同的默认密钥生成器,您需要实现org.springframework.cache.interceptor.KeyGenerator
接口。
默认的密钥生成策略随着 Spring 4.0 的发布而改变。早些时候
版本使用 Key 生成策略,对于多个 Key 参数,
仅被视为 如果您想继续使用之前的密钥策略,可以将已弃用的 |
自定义密钥生成声明
由于缓存是泛型的,因此目标方法很可能具有各种签名 这不能轻易地映射到缓存结构之上。这往往变得很明显 当 target 方法具有多个参数,其中只有一些参数适合 缓存(而其余部分仅由 method logic使用)。请考虑以下示例:
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
乍一看,虽然两者boolean
论点影响了这本书的发现方式,
它们对缓存没有用处。此外,如果两者中只有一个很重要怎么办
而另一个则不是?
对于此类情况,@Cacheable
annotation 允许您指定密钥的生成方式
通过其key
属性。您可以使用 SpEL 选择
感兴趣的参数(或其嵌套属性)、执行作,甚至
调用任意方法,而无需编写任何代码或实现任何接口。
这是默认生成器
因为随着代码库的增长,方法的签名往往有很大的不同。虽然
default 策略可能适用于某些方法,但很少适用于所有方法。
以下示例使用各种 SPEL 声明(如果您不熟悉 SPEL, 帮自己一个忙,读一读 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)
前面的代码片段显示了选择某个参数(其 属性,甚至是任意(静态)方法。
如果负责生成密钥的算法过于具体,或者需要
要共享,您可以定义自定义keyGenerator
在行动中。为此,
指定KeyGenerator
要使用的 bean 实现,如下所示
示例显示:
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
这key 和keyGenerator parameters 是互斥的,并且
指定 both 将导致异常。 |
默认缓存分辨率
缓存抽象使用一个简单的CacheResolver
检索缓存
在作级别使用配置的CacheManager
.
要提供不同的默认缓存解析器,您需要实现org.springframework.cache.interceptor.CacheResolver
接口。
自定义缓存分辨率
默认缓存分辨率非常适合使用
单CacheManager
并且没有复杂的缓存分辨率要求。
对于使用多个缓存管理器的应用程序,您可以设置cacheManager
用于每个作,如下例所示:
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") (1)
public Book findBook(ISBN isbn) {...}
1 | 指定anotherCacheManager . |
您还可以将CacheResolver
完全以类似于
替换密钥生成。
每个缓存作都会请求 resolution,让 implementation
实际上,根据运行时参数解析要使用的缓存。以下示例
演示如何指定CacheResolver
:
@Cacheable(cacheResolver="runtimeCacheResolver") (1)
public Book findBook(ISBN isbn) {...}
1 | 指定CacheResolver . |
从 Spring 4.1 开始, 与 |
同步缓存
在多线程环境中,可能会同时调用 相同的参数(通常在启动时)。默认情况下,缓存抽象不会 锁定任何内容,相同的值可能会被多次计算,从而违背目的 缓存。
对于这些特定情况,您可以使用sync
属性来指示底层
cache provider 在计算值时锁定缓存条目。因此,
只有一个线程忙于计算该值,而其他线程被阻塞,直到进入
在缓存中更新。以下示例演示如何使用sync
属性:
@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 | 使用sync 属性。 |
这是一项可选功能,您最喜欢的缓存库可能不支持它。
都CacheManager 核心框架提供的实现支持它。请参阅
缓存提供商的文档,了解更多详细信息。 |
条件缓存
有时,某个方法可能并不适合一直进行缓存(例如,它可能
取决于给定的参数)。缓存注释通过condition
参数,该参数采用SpEL
表达式,该表达式的计算结果为true
或false
.如果true
,则该方法将被缓存。如果不是,则它的行为就像方法不是一样
cached (即,无论缓存中的值是什么,每次都会调用该方法
或使用的参数)。例如,仅当
论点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
return 类型。如果Optional
价值
存在,它将存储在关联的缓存中。如果Optional
value 不是
目前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 求值上下文
每SpEL
expression 根据专用的context
.
除了内置参数外,框架还提供了专用的缓存相关
元数据,例如参数名称。下表描述了制造的项目
available to context 中,以便您可以将它们用于键和条件计算:
名字 | 位置 | 描述 | 例 |
---|---|---|---|
|
根对象 |
正在调用的方法的名称 |
|
|
根对象 |
正在调用的方法 |
|
|
根对象 |
正在调用的目标对象 |
|
|
根对象 |
正在调用的目标的类 |
|
|
根对象 |
用于调用目标的参数(作为数组) |
|
|
根对象 |
运行当前方法所针对的缓存的集合 |
|
参数名称 |
评估上下文 |
任何方法参数的名称。如果名称不可用
(可能是由于没有调试信息),参数名称也可以在 |
|
|
评估上下文 |
方法调用的结果(要缓存的值)。仅适用于 |
|
这@CachePut
注解
当需要更新缓存而不干扰方法执行时,
您可以使用@CachePut
注解。也就是说,该方法始终被调用,并且其
result 被放入缓存中(根据@CachePut
选项)。它支持
与@Cacheable
,并且应该用于缓存填充,而不是
方法流程优化。以下示例使用@CachePut
注解:
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
用@CachePut 和@Cacheable 相同方法上的 annotation 通常是
强烈反对,因为他们的行为不同。虽然后者会导致
方法调用,前者强制在
运行缓存更新。这会导致意外行为,并且除了
的特定极端情况(例如,具有将它们从每个
other),则应避免使用此类声明。另请注意,这些条件不应依赖于
在 result 对象上(即#result 变量),因为这些是预先验证的
确认排除项。 |
这@CacheEvict
注解
缓存抽象不仅允许填充缓存存储,还允许逐出。
此过程可用于从缓存中删除过时或未使用的数据。与@Cacheable
,@CacheEvict
划分执行缓存的方法
逐出(即充当从缓存中删除数据的触发器的方法)。
与它的兄弟姐妹类似,@CacheEvict
需要指定一个或多个缓存
,则允许自定义缓存和密钥解析或
条件,并具有一个额外的参数
(allEntries
),指示是否需要执行缓存范围的逐出
而不仅仅是条目驱逐(基于键)。以下示例驱逐
来自books
缓存:
@CacheEvict(cacheNames="books", allEntries=true) (1)
public void loadBooks(InputStream batch)
1 | 使用allEntries 属性从缓存中逐出所有条目。 |
当需要清除整个缓存区域时,此选项会派上用场。 而不是逐出每个条目(这将花费很长时间,因为它效率低下), 如前面的示例所示,所有条目都在一个作中删除。 请注意,框架会忽略此方案中指定的任何键,因为它不适用 (整个缓存被驱逐,而不仅仅是一个条目)。
您还可以指示驱逐是应该在 (默认) 之后还是之前进行
该方法通过使用beforeInvocation
属性。前者提供
与其他 Comments 的语义相同:方法成功完成后,
对缓存运行作(在本例中为 Eviction)。如果该方法没有
run(因为它可能被缓存)或引发异常,则不会发生驱逐。
后者 (beforeInvocation=true
) 导致驱逐始终发生在
方法。这在不需要绑定驱逐的情况下非常有用
到方法结果。
请注意,void
方法可以与@CacheEvict
- 由于这些方法充当
trigger,则返回值将被忽略(因为它们不与缓存交互)。这是
但情况并非如此@Cacheable
将数据添加到缓存中或更新缓存中的数据
因此,需要一个结果。
这@Caching
注解
有时,同一类型的多个注解(例如@CacheEvict
或@CachePut
) — 例如,因为 condition 或 key
expression 在不同缓存之间是不同的。@Caching
允许多个嵌套@Cacheable
,@CachePut
和@CacheEvict
annotations 可以使用相同的方法。
以下示例使用两个@CacheEvict
附注:
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
这@CacheConfig
注解
到目前为止,我们已经看到缓存作提供了许多自定义选项,并且
您可以为每个作设置这些选项。但是,一些自定义选项
如果它们应用于类的所有作,则配置起来可能会很繁琐。为
实例,指定要用于
class 可以替换为单个类级定义。这是@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 豆
例如,在CaffeineCacheManager .这些实际上也是全球性的。 |
启用缓存注释
请务必注意,即使声明缓存注释不会 自动触发他们的作 - 就像 Spring 中的许多事情一样,该功能必须 声明式启用(这意味着如果您怀疑缓存是罪魁祸首,您可以 通过仅删除 1 个 configuration 行而不是 您的代码)。
要启用缓存注释,请添加注释@EnableCaching
到你的@Configuration
类:
@Configuration
@EnableCaching
public 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
annotation 允许您
指定影响缓存行为添加到
通过 AOP 申请。该配置有意与@Transactional
.
处理缓存 annotation 的默认通知模式是proxy ,它允许
仅用于通过代理拦截呼叫。同一类中的本地呼叫
不能以这种方式被拦截。对于更高级的拦截模式,请考虑
切换到aspectj 模式与编译时或加载时编织结合使用。 |
有关高级自定义 (使用 Java 配置) 的更多详细信息,这些自定义
需要实施CachingConfigurer ,请参阅 Javadoc。 |
XML 属性 | 注释属性 | 违约 | 描述 |
---|---|---|---|
|
N/A(请参阅 |
|
要使用的缓存管理器的名称。默认的 |
|
N/A(请参阅 |
一个 |
用于解析后备缓存的CacheResolver的 Bean 名称。 此属性不是必需的,只需指定为 'cache-manager' 属性。 |
|
N/A(请参阅 |
|
要使用的自定义密钥生成器的名称。 |
|
N/A(请参阅 |
|
要使用的自定义缓存错误处理程序的名称。默认情况下,在 与缓存相关的作将退回到客户端。 |
|
|
|
默认模式 ( |
|
|
|
仅适用于代理模式。控制为什么类型的缓存代理创建
类中带有 |
|
|
Ordered.LOWEST_PRECEDENCE |
定义应用于 Comments 为 的 bean 的缓存通知的顺序 |
<cache:annotation-driven/> 查找@Cacheable/@CachePut/@CacheEvict/@Caching 仅在定义它的同一应用程序上下文中的 bean 上。这意味着,
如果您将<cache:annotation-driven/> 在WebApplicationContext 对于DispatcherServlet ,它只检查控制器中的 bean,而不检查服务中的 bean。
有关更多信息,请参阅 MVC 部分。 |
Spring 建议您只注释具体的类(以及 concrete 的方法
类)与@Cache* 注释,而不是注释接口。
您当然可以放置一个@Cache* 接口(或接口)上的 Comments
方法),但这仅在您使用代理模式 (mode="proxy" ).如果您使用
基于编织的方面 (mode="aspectj" ),则无法识别
由 Weaving Infrastructure 提供的接口级声明。 |
在代理模式(默认)下,只有通过
proxy 被拦截。这意味着自调用(实际上是
target 对象调用目标对象的另一个方法)不会导致实际的
缓存,即使调用的方法标记为@Cacheable .考虑
使用aspectj 模式。此外,代理必须完全初始化为
提供预期的行为,因此您不应在
初始化代码(即@PostConstruct ). |
使用自定义注释
缓存抽象允许您使用自己的 Comments 来识别方法。
触发缓存填充或驱逐。作为模板机制,这非常方便,
因为它消除了复制缓存注释声明的需要,即
如果指定了 key 或 condition,或者 foreign imports
(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 注解,则容器会自动选取
up 它的声明,并理解它的含义。请注意,如前所述,需要启用 Comments 驱动的行为。