|
对于最新稳定版本,请使用Spring Framework 7.0.1! |
基于声明式注释的缓存
对于缓存声明,Spring的缓存抽象提供了一组Java注释:
-
@Cacheable:触发缓存人口。 -
@CacheEvict: 触发缓存驱逐。 -
@CachePut: 更新缓存而不干扰方法的执行。 -
@Caching:将多个缓存作重新组合,用于应用于一个方法。 -
@CacheConfig:在类别级别共享一些常见的缓存相关设置。
这@Cacheable注解
顾名思义,你可以使用@Cacheable用来划分可缓存的方法——即结果存储在缓存中的方法,使得在后续
调用(参数相同),缓存中的值会返回
不得不实际调用这个方法。最简单的形式是注释声明
需要与注释方法关联的缓存名称,具体如下
示例如下:
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
在前面的摘录中,findBook方法与名为书.
每次调用该方法时,都会检查缓存,看看调用是否具有
已经运行过,无需重复。而在大多数情况下,只有一个
声明缓存后,注释允许指定多个名称,以便多个
缓存正在使用。在这种情况下,每个缓存都会在调用
方法——如果至少触发一个缓存,则返回相应的值。
| 所有不包含该值的其他缓存也会被更新,尽管 缓存方法实际上并未被调用。 |
以下示例使用@Cacheable在findBook多缓存方法:
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
默认密钥生成
由于缓存本质上是键值存储,每次调用缓存方法时
需要被转换为适合缓存访问的密钥。缓存抽象
使用简单的密钥生成器基于以下算法:
-
如果没有参数,则返回
简单钥匙.EMPTY. -
如果只给出一个参数,返回该实例。
-
如果给定多个参数,返回
SimpleKey包含所有参数。
只要参数具有自然密钥,这种方法在大多数用例中都有效
并实现有效hashCode()和等值()方法。如果不是这样,
你需要改变策略。
要提供不同的默认密钥生成器,你需要实现org.springframework.cache.interceptor.KeyGenerator接口。
|
默认密钥生成策略随着春季4.0的发布发生了变化。早些时候
Spring的版本采用了密钥生成策略,对于多个关键参数,
仅考虑 如果你想继续使用之前的密钥策略,可以配置已弃用的 |
自定义密钥生成声明
由于缓存是通用的,目标方法很可能会有不同的签名 这无法轻易地映射到缓存结构之上。这一点很容易显而易见 当目标方法有多个参数,其中只有部分参数适合 缓存(其余部分仅由方法逻辑使用)。请考虑以下例子:
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
乍一看,当两人布尔论点影响了书籍的呈现方式,
它们对缓存没用。此外,如果两者中只有一个重要呢
而另一个则不在?
对于此类情况,@Cacheable注释功能可以让你指定密钥的生成方式
通过其钥匙属性。你可以用SpEL来选择
感兴趣的参数(或其嵌套属性)、执行作,甚至
调用任意方法,无需编写代码或实现任何接口。
这是推荐的替代默认生成器的方法,
因为随着代码库的扩展,方法的签名通常会有很大差异。虽然
默认策略可能适用于某些方法,但很少适用于所有方法。
以下示例使用了各种 SpEL 声明(如果你不熟悉 SpEL, 帮自己一个忙,去读读《春季表达语言》):
@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关于手术。为此,
指定密钥生成器BEAN 实现用于以下内容
示例如下:
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
这钥匙和keyGenerator参数互斥且是作
该条件说明了两者都会产生例外。 |
默认缓存分辨率
缓存抽象使用简单的缓存解析器它会检索缓存
在作层面通过使用配置的缓存管理器.
要提供不同的默认缓存解析器,你需要实现org.springframework.cache.interceptor.CacheResolver接口。
自定义缓存解析
默认缓存分辨率非常适合与
单缓存管理器并且没有复杂的缓存分辨率要求。
对于支持多个缓存管理器的应用程序,你可以设置缓存管理器用于每个作,如下示例所示:
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") (1)
public Book findBook(ISBN isbn) {...}
| 1 | 指定anotherCacheManager(另一个缓存管理器). |
你也可以替换缓存解析器完全以类似
替换密钥生成。
每次缓存作都会请求解析,以便实现
实际上是基于运行时参数解析缓存来使用。以下示例
展示了如何指定 a缓存解析器:
@Cacheable(cacheResolver="runtimeCacheResolver") (1)
public Book findBook(ISBN isbn) {...}
| 1 | 具体说明缓存解析器. |
|
自4.1春季以来, 类似于 |
同步缓存
在多线程环境中,某些作可能会同时调用 同样的论点(通常是在启动时)。默认情况下,缓存抽象并不支持 锁定任何东西,且相同的值可能会被多次计算,从而破坏初度 关于缓存。
对于这些特定情况,你可以使用同步属性来指示底层
缓存提供者在计算值时锁定缓存条目。因此,
只有一个线程忙于计算该值,其他线程则被阻塞直到条目
在缓存中更新。以下示例展示了如何使用同步属性:
@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
| 1 | 使用同步属性。 |
这是可选功能,你喜欢的缓存库可能不支持。
都缓存管理器核心框架提供的实现支持该技术。参见
需要查看缓存提供商的文档以获取更多细节。 |
使用CompletableFuture和Reactive Return类型缓存
截至6.1版本,缓存注释为完成未来以及响应式回流类型
考虑到,自动调整缓存交互。
对于返回完成未来,由该未来产生的对象
缓存完成时 会被缓存,缓存命中时的缓存查找也会
通过完成未来:
@Cacheable("books")
public CompletableFuture<Book> findBook(ISBN isbn) {...}
对于返回反应堆的方法单,即该反应流发射的对象
发布者会在可用时缓存,缓存查询
命中将被检索为单(由完成未来):
@Cacheable("books")
public Mono<Book> findBook(ISBN isbn) {...}
对于返回反应堆的方法通量,即由该反应流发射的对象
出版商将被收集到一个列表并且在该列表完成时缓存,
缓存查询将以通量(由完成未来对于缓存的列表价值):
@Cacheable("books")
public Flux<Book> findBooks(String author) {...}
这样完成未来响应式适应也适用于同步缓存,
在同时缓存未命中时只计算一次该值:
@Cacheable(cacheNames="foos", sync=true) (1)
public CompletableFuture<Foo> executeExpensiveOperation(String id) {...}
| 1 | 使用同步属性。 |
为了让这种安排在运行时正常工作,配置好的缓存
需要具备完成未来基于检索。泉水提供的ConcurrentMapCacheManager自动适应这种检索方式,CaffeineCacheManager当其异步缓存模式为
启用:设置setAsyncCacheMode(true)在你的CaffeineCacheManager实例。 |
@Bean
CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCacheSpecification(...);
cacheManager.setAsyncCacheMode(true);
return cacheManager;
}
最后但同样重要的是,要注意注释驱动的缓存并不合适
用于涉及成分和背压的复杂反应性相互作用。
如果你选择申报@Cacheable在特定的响应式方法上,考虑
较粗粒度缓存交互的影响,该相互作用仅存储
对于 a 的发射对象单甚至是预先收集的对象列表通量.
条件缓存
有时,某种方法可能不适合一直缓存(例如,可能
取决于给定的论证)。缓存注释通过条件参数,该参数取一个SpEL被评估为以下true或false.如果true,方法被缓存。如果不是,则表现为方法本身不存在
缓存(即无论缓存中值如何,每次都会调用该方法)
或者使用了哪些论证)。例如,以下方法只有在
论点名称长度短于32:
@Cacheable(cacheNames="book", condition="#name.length() < 32") (1)
public Book findBook(String name)
| 1 | 在 上设置条件@Cacheable. |
此外条件参数,你可以使用除非否决 的参数
向缓存添加一个值。与条件,除非表达式被评估
在方法被调用之后。扩展到前面的例子,也许我们只是
想要缓存平装书,正如以下例子所示:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") (1)
public Book findBook(String name)
| 1 | 使用除非归于硬壳方块。 |
缓存抽象支持java.util.Optional返回类型。如果自选价值
存在,它会被存储在关联的缓存中。如果自选价值不是
目前零将存储在关联的缓存中。#result总是指
商业实体,且从未被支持封装,因此之前的示例可以重写
如下:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)
注意#result仍然指书且可选<书>.因为可能是零我们使用 SpEL 的安全导航操作员。
可用的缓存 SpEL 评估上下文
每SpEL表达式对专用上下文.
除了内置参数外,该框架还提供专用的缓存相关功能
元数据,比如参数名称。下表描述了制作的物品
上下文中可用,以便你用它们进行密钥和条件计算:
| 名称 | 位置 | 描述 | 示例 |
|---|---|---|---|
|
根对象 |
被引用的方法名称 |
|
|
根对象 |
所引用的方法 |
|
|
根对象 |
被调用的目标对象 |
|
|
根对象 |
被调用的目标类别 |
|
|
根对象 |
用于调用目标的参数(作为对象数组) |
|
|
根对象 |
当前方法运行的缓存集合 |
|
论元名称 |
评估背景 |
某个特定方法论元的名称。如果名字不可得
(例如,因为代码编译时没有 |
|
|
评估背景 |
方法调用的结果(要缓存的值)。仅有 |
|
这@CachePut注解
当缓存需要在不干扰方法执行的情况下更新时,
你可以使用@CachePut注解。也就是说,该方法总是被调用,并且其
结果被放入缓存中(根据@CachePut选项)。它支持
与@Cacheable应用于缓存填充,而非
方法流程优化。以下示例使用了@CachePut注解:
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
用@CachePut和@Cacheable同一方法的注释通常为
强烈不鼓励,因为它们的行为不同。而后者则导致
如果要通过使用缓存跳过方法调用,前者强制调用
命令执行缓存更新。这导致了意外的行为,除了
特定角落情况的说明(例如具有排除这些条件的注释
其他),应避免此类声明。还要注意,这些条件不应依赖
对结果对象(即#result变量),因为这些在前被验证为
确认排除。 |
截至6.1,@CachePut需要完成未来以及考虑响应式回报类型,
只要产生了对象可用,就执行该作。
这@CacheEvict注解
缓存抽象不仅允许缓存存储的填充,还支持逐出。
该过程有助于从缓存中移除过时或未使用的数据。相对于@Cacheable,@CacheEvict标记执行缓存的方法
驱逐(即作为触发器从缓存中移除数据的方法)。
与其兄弟类似,@CacheEvict需要指定一个或多个缓存
受动作影响的,允许自定义缓存和密钥解析,或
条件需指定,并具有一个额外的参数
(所有参赛作品)指示是否需要执行全缓存驱逐
而不是仅仅基于钥匙的进入淘汰。以下示例驱逐
所有来自书缓存:
@CacheEvict(cacheNames="books", allEntries=true) (1)
public void loadBooks(InputStream batch)
| 1 | 使用所有参赛作品属性 以驱逐缓存中的所有条目。 |
当需要清理整个缓存区域时,这个选项非常有用。 与其逐条逐条逐项淘汰(那样效率低,耗时很长), 所有元素在一次作中被移除,如前例所示。 请注意,框架在此场景中忽略了指定的任何密钥,因为该密钥不适用 (整个缓存被淘汰,而不仅仅是一个条目。)
你也可以说明驱逐是在默认之后还是之前进行
该方法通过使用祈祷之前属性。前者提供了
与其他注释的语义相同:一旦方法成功完成,
对缓存执行一个作(此处为驱逐)。如果方法不成立
运行(因为可能被缓存)或抛出异常,则不会发生驱逐。
后者(beforeInvocation=真导致驱逐总是发生在
方法被调用。这在驱逐不需要打平的情况下非常有用
对方法结果的判断。
注意无效方法可用于@CacheEvict- 因为方法的作用是
触发时,返回值被忽略(因为它们不与缓存交互)。这是
但与@Cacheable这会向缓存中添加数据或更新缓存中的数据
因此,需要一个结果。
截至6.1,@CacheEvict需要完成未来以及考虑响应式回报类型,
每当处理完成后执行一次调用后的驱逐作。
这@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是一种类级注释,允许共享缓存名称,
习俗密钥生成器,习俗缓存管理器,以及惯例缓存解析器.
在类上放置该注释不会启动任何缓存作。
作级自定义总是覆盖 的自定义集@CacheConfig.
因此,每个缓存作可进行三层自定义:
-
全局配置,例如通过
缓存配置器:见下一节。 -
在类别层面,使用
@CacheConfig. -
在行动层面。
通常提供服务提供者特定的设置缓存管理器豆
例如,在CaffeineCacheManager.这些实际上也是全球性的。 |
启用缓存注释
需要注意的是,尽管声明缓存注释并不意味着 自动触发他们的动作——像春季的许多功能一样,这个功能必须是 声明式启用(这意味着如果你怀疑缓存是原因,可以 只需移除一行配置,而不是移除所有注释,即可禁用它 你的代码)。
要启用缓存注释,请添加注释@EnableCaching给你的其中一位@Configuration类:
@Configuration
@EnableCaching
class AppConfig {
@Bean
CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCacheSpecification(...);
return cacheManager;
}
}
另外,对于XML配置,你可以使用缓存:注释驱动元素:
<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>
两者缓存:注释驱动元素和@EnableCaching注释可以让你
指定各种影响缓存行为添加方式的选项
通过AOP申请。该构型有意与@Transactional.
处理缓存注释的默认建议模式为代理,这使得
仅用于通过代理拦截通话。同一类内的本地呼叫
不能被拦截。对于更高级的截获模式,考虑
切换至Aspectj模式与编译时或加载时织入结合。 |
关于高级定制(使用Java配置)的更多细节,以下内容包括
实施的必要条件缓存配置器,请参见Javadoc。 |
| XML属性 | 注释属性 | 默认值 | 描述 |
|---|---|---|---|
|
不适用(参见 |
|
缓存管理器的名称。默认 |
|
不适用(参见 |
一个 |
用于解析后备缓存的缓存解析器的豆名。 该属性并非必需,只需作为替代方案指定 即“缓存管理器”属性。 |
|
不适用(参见 |
|
自定义密钥生成器的名称。 |
|
不适用(参见 |
|
自定义缓存错误处理程序的名称。默认情况下,任何在 缓存相关的作会被抛回客户端。 |
|
|
|
默认模式( |
|
|
|
仅适用于代理模式。控制为何种类型的缓存代理创建
用 |
|
|
Ordered.LOWEST_PRECEDENCE |
定义了对注释为 的豆子应用缓存建议的顺序 |
<缓存:注释驱动/>寻找@Cacheable/@CachePut/@CacheEvict/@Caching仅限于定义的同一应用上下文中的豆子。这意味着,
如果你放<缓存:注释驱动/>在WebApplicationContext对于调度器服务它只检查你的控制器中的豆子,而不是你的服务。
更多信息请参见MVC部分。 |
Spring建议你只注释具体类(以及具体类的方法)
类)中@Cache*注释,而不是注释接口。
你当然可以设置@Cache*接口上的注释(或接口)
方法),但这只在你使用代理模式时才有效(mode=“代理”).如果你使用了
基于织造的方面(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 {
}
在前面的例子中,我们定义了自己的慢速服务注解
该符号本身被注释为@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 注释,容器会自动选择
运行时要理解它的声明并理解它的含义。注意,如前所述,需要启用注释驱动的行为。