对于缓存声明,Spring 的缓存抽象提供了一组 Java 注解:
-
@Cacheable
:触发缓存填充。 -
@CacheEvict
:触发缓存逐出。 -
@CachePut
:在不干扰方法执行的情况下更新缓存。 -
@Caching
:重新组合要应用于方法的多个缓存操作。 -
@CacheConfig
:在类级别共享一些常见的缓存相关设置。
注释@Cacheable
顾名思义,您可以使用来划分可缓存的方法,即将结果存储在缓存中的方法,以便在随后的
调用(使用相同的参数),则返回缓存中的值,而没有
必须实际调用该方法。在最简单的形式中,注释声明
需要与带批注的方法关联的缓存的名称,如下所示
示例显示:@Cacheable
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
在前面的代码片段中,该方法与名为 的缓存相关联。
每次调用该方法时,都会检查缓存以查看调用是否具有
已经运行,不必重复。而在大多数情况下,只有一个
cache 被声明,注解允许指定多个名称,以便多个名称
正在使用缓存。在这种情况下,在调用
method — 如果至少命中一个缓存,则返回关联的值。findBook
books
不包含该值的所有其他缓存也会更新,即使 缓存的方法实际上没有被调用。 |
以下示例在具有多个缓存的方法上使用:@Cacheable
findBook
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
默认密钥生成
由于缓存本质上是键值存储,因此每次调用缓存方法
需要转换为适合缓存访问的密钥。缓存抽象
使用基于以下算法的简单算法:KeyGenerator
-
如果未给出任何参数,则返回 。
SimpleKey.EMPTY
-
如果只给出一个参数,则返回该实例。
-
如果给定了多个参数,则返回包含所有参数的 a。
SimpleKey
这种方法适用于大多数用例,只要参数具有自然键即可
并实现有效和方法。如果不是这种情况,
你需要改变策略。hashCode()
equals()
要提供不同的默认密钥生成器,您需要实现该接口。org.springframework.cache.interceptor.KeyGenerator
默认密钥生成策略随着 Spring 4.0 的发布而更改。早些时候
Spring 的版本使用密钥生成策略,对于多个密钥参数,
只考虑了 of 参数,而不是 .这可能会导致
意外的键冲突(背景参见 spring-framework#14870)。对于此类方案,新版本使用复合键。 如果要继续使用以前的密钥策略,可以配置已弃用的类或创建自定义
基于哈希的实现。 |
自定义密钥生成声明
由于缓存是通用的,因此目标方法很可能具有各种签名 这不能很容易地映射到缓存结构的顶部。这往往变得很明显 当目标方法具有多个参数时,其中只有一些参数适合 缓存(而其余的仅由方法逻辑使用)。请看以下示例:
@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
乍一看,虽然这两个论点影响了这本书的发现方式,
它们对缓存没有用处。此外,如果两者中只有一个很重要怎么办
而另一个不是?boolean
对于此类情况,注释允许您指定密钥的生成方式
通过其属性。您可以使用 SpEL 来选择
感兴趣的参数(或其嵌套属性),执行操作,甚至
调用任意方法,而无需编写任何代码或实现任何接口。
这是默认生成器的推荐方法,
因为随着代码库的增长,签名的方法往往大不相同。虽然
默认策略可能适用于某些方法,但很少适用于所有方法。@Cacheable
key
以下示例使用各种 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)
前面的代码片段显示了选择某个参数是多么容易,其中一个参数 属性,甚至是任意(静态)方法。
如果负责生成密钥的算法过于具体,或者需要
要共享,您可以在操作上定义自定义。为此,
指定要使用的 Bean 实现的名称,如下所示
示例显示:keyGenerator
KeyGenerator
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
和 参数是互斥的,并且是运算
这指定了异常中的两种结果。key keyGenerator |
默认缓存分辨率
缓存抽象使用检索缓存的简单方法
使用配置的 .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 开始,缓存注解的属性不再
强制性的,因为无论注释的内容如何,都可以提供此特定信息。 与 和 类似,和 参数是互斥的,并且指定两者的操作
导致异常,因为实现会忽略自定义。这可能不是你所期望的。 |
同步缓存
在多线程环境中,可以同时调用某些操作 相同的参数(通常在启动时)。默认情况下,缓存抽象不会 锁定任何东西,并且可以多次计算相同的值,从而破坏目的 的缓存。
对于这些特殊情况,您可以使用该属性来指示基础
缓存提供程序,用于在计算值时锁定缓存条目。因此,
只有一个线程忙于计算值,而其他线程则被阻止,直到进入
在缓存中更新。下面的示例演示如何使用该属性:sync
sync
@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 | 使用属性。sync |
这是一项可选功能,您喜欢的缓存库可能不支持它。
核心框架提供的所有实现都支持它。请参阅
有关更多详细信息的缓存提供程序的文档。CacheManager |
使用 CompletableFuture 和 Reactive 返回类型进行缓存
从 6.1 开始,缓存注释采用和响应式返回类型
考虑在内,自动相应地调整缓存交互。CompletableFuture
对于返回 的方法,该 future 生成的对象
每当它完成时都会被缓存,并且缓存命中的缓存查找将
通过以下方式检索:CompletableFuture
CompletableFuture
@Cacheable("books")
public CompletableFuture<Book> findBook(ISBN isbn) {...}
对于返回 Reactor 的方法,该 Reactive Streams 发出的对象
只要发布者可用,就会缓存它,并且缓存查找缓存
命中将被检索为(由 A 支持):Mono
Mono
CompletableFuture
@Cacheable("books")
public Mono<Book> findBook(ISBN isbn) {...}
对于返回 Reactor 的方法,该反应流发出的对象
Publisher 将被收集到 A 中,并在该列表完成时缓存,
缓存命中的缓存查找将检索为 (缓存值由 A 支持):Flux
List
Flux
CompletableFuture
List
@Cacheable("books")
public Flux<Book> findBooks(String author) {...}
这种反应式适配也适用于同步缓存,
在并发缓存未命中的情况下仅计算一次值:CompletableFuture
@Cacheable(cacheNames="foos", sync=true) (1)
public CompletableFuture<Foo> executeExpensiveOperation(String id) {...}
1 | 使用属性。sync |
为了使这种安排在运行时起作用,配置的缓存
需要能够进行基于 -的检索。Spring 提供的会自动适应该检索样式,并在其异步缓存模式为
Enabled:在实例上设置。CompletableFuture ConcurrentMapCacheManager CaffeineCacheManager setAsyncCacheMode(true) CaffeineCacheManager |
@Bean
CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCacheSpecification(...);
cacheManager.setAsyncCacheMode(true);
return cacheManager;
}
最后但并非最不重要的一点是,请注意注释驱动的缓存是不合适的
用于涉及成分和背压的复杂反应相互作用。
如果选择在特定的反应式方法上声明,请考虑
相当粗粒度的缓存交互的影响,它只是存储
发出的对象,甚至是 .@Cacheable
Mono
Flux
条件缓存
有时,某个方法可能不适合一直缓存(例如,它可能
取决于给定的参数)。缓存注释通过参数支持此类用例,该参数采用计算结果为 或 的表达式。如果 ,则缓存该方法。如果不是,它的行为就好像方法不是
cached(即,无论缓存中有什么值,每次都会调用该方法
或使用什么参数)。例如,仅当
参数的长度短于 32:condition
SpEL
true
false
true
name
@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)
请注意,仍然是指而不是 .既然可能是,我们使用 SpEL 的安全导航算子。#result
Book
Optional<Book>
null
可用的缓存 SpEL 评估上下文
每个表达式都根据专用上下文
进行计算。
除了内置参数外,该框架还提供专用的缓存相关
元数据,例如参数名称。下表描述了制作的项目
可用于上下文,以便您可以将它们用于键和条件计算:SpEL
名字 | 位置 | 描述 | 例 |
---|---|---|---|
|
根对象 |
正在调用的方法的名称 |
|
|
根对象 |
正在调用的方法 |
|
|
根对象 |
正在调用的目标对象 |
|
|
根对象 |
要调用的目标的类 |
|
|
根对象 |
用于调用目标的参数(作为数组) |
|
|
根对象 |
运行当前方法的缓存的集合 |
|
参数名称 |
评估背景 |
任何方法参数的名称。如果名称不可用
(可能是由于没有调试信息),参数名称也可以在参数索引的 where 代表下找到(从 开始)。 |
|
|
评估背景 |
方法调用的结果(要缓存的值)。仅在表达式、表达式(用于计算 )或表达式(当 是 时)中可用。对于支持的包装器(如 ),指的是实际对象,而不是包装器。 |
|
注释@CachePut
当需要在不干扰方法执行的情况下更新缓存时,
您可以使用批注。也就是说,该方法始终被调用,其
结果被放入缓存中(根据选项)。它支持
相同的选项,并且应该用于缓存填充,而不是
方法流程优化。以下示例使用批注:@CachePut
@CachePut
@Cacheable
@CachePut
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
使用和注释相同的方法一般是
强烈劝阻,因为他们有不同的行为。而后者导致
使用缓存跳过方法调用,前者强制调用
命令运行缓存更新。这会导致意外行为,并且,例外
特定的极端情况(例如,具有将它们排除在每个极端情况之外的条件的注释
其他),应避免此类声明。另请注意,此类条件不应依赖
在结果对象(即变量)上,因为这些对象是预先验证的
确认排除项。@CachePut @Cacheable #result |
从 6.1 开始,考虑了反应式返回类型,
每当生成的对象可用时,就执行 put 操作。@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
注释@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
-
在操作层面。
特定于提供程序的设置通常在 Bean 上可用,
例如,在 .这些实际上也是全球性的。CacheManager CaffeineCacheManager |
启用缓存注释
需要注意的是,即使声明缓存注释不会 自动触发他们的操作 - 就像 Spring 中的许多事情一样,该功能必须是 声明式启用(这意味着如果您怀疑缓存是罪魁祸首,您可以 通过仅删除一个配置行而不是 您的代码)。
要启用缓存批注,请将批注添加到其中一个类中:@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>
元素和注释都允许您
指定影响缓存行为添加到
通过AOP申请。该配置有意与@Transactional
的配置相似。cache:annotation-driven
@EnableCaching
处理缓存注释的默认建议模式是 ,它允许
仅用于通过代理拦截呼叫。同一类中的本地呼叫
不能以这种方式被拦截。对于更高级的拦截模式,请考虑
切换到与编译时或加载时编织相结合的模式。proxy aspectj |
有关高级定制(使用 Java 配置)的更多详细信息,这些定制是
需要实现,请参阅 Javadoc。CachingConfigurer |
XML 属性 | Annotation 属性 | 违约 | 描述 |
---|---|---|---|
|
不适用(请参阅 |
|
要使用的缓存管理器的名称。默认值在后面初始化
具有此缓存管理器的场景(或者如果未设置)。查看更多
缓存分辨率的细粒度管理,请考虑设置“缓存解析器”
属性。 |
|
不适用(请参阅 |
A 使用配置的 . |
用于解析后备缓存的 CacheResolver 的 Bean 名称。 此属性不是必需的,只需指定为替代属性 “cache-manager”属性。 |
|
不适用(请参阅 |
|
要使用的自定义密钥生成器的名称。 |
|
不适用(请参阅 |
|
要使用的自定义缓存错误处理程序的名称。默认情况下,在 与缓存相关的操作将返回客户端。 |
|
|
|
默认模式 () 使用 Spring 的 AOP 处理要代理的带注释的 Bean
框架(如前所述,遵循代理语义,适用于方法调用
仅通过代理进入)。替代模式 () 将
受 Spring 的 AspectJ 缓存方面影响的类,修改目标类字节
应用于任何类型的方法调用的代码。AspectJ 编织需要在类路径中启用加载时编织(或编译时编织)。(有关如何设置的详细信息,请参阅 Spring 配置
加载时间编织。 |
|
|
|
仅适用于代理模式。控制为哪些类型的缓存代理创建
使用 OR 注释注释的类。如果该属性设置为 ,则创建基于类的代理。
如果 是 或如果省略该属性,则标准 JDK
创建基于接口的代理。(有关不同代理类型的详细检查,请参阅代理机制。 |
|
|
Ordered.LOWEST_PRECEDENCE |
定义应用于用 或 注释的 Bean 的缓存建议的顺序。(有关与以下内容相关的规则的更多信息
订购 AOP 建议,请参阅建议订购。
没有指定的顺序意味着 AOP 子系统确定建议的顺序。 |
<cache:annotation-driven/> 仅在定义 Bean 的同一应用程序上下文中查找 Bean。这意味着,
如果您输入 A for a ,它只检查控制器中的 Bean,而不是服务中的 Bean。
有关详细信息,请参阅 MVC 部分。@Cacheable/@CachePut/@CacheEvict/@Caching <cache:annotation-driven/> WebApplicationContext DispatcherServlet |
Spring 建议你只注释具体类(和具体的方法
classes) 与注释接口相反。
您当然可以在接口(或接口)上放置注释
方法),但这仅在使用代理模式 () 时才有效。如果使用
基于编织的方面 (),缓存设置在
Weaving 基础结构的接口级声明。@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)
即使不是 Spring 注解,容器也会自动拾取
在运行时声明并理解其含义。请注意,如前所述,需要启用注释驱动的行为。@SlowService