对于最新的稳定版本,请使用 Spring Framework 6.2.0spring-doc.cn

基于声明性注释的缓存

对于缓存声明, Spring 的缓存抽象提供了一组 Java 注释:spring-doc.cn

  • @Cacheable:触发缓存填充。spring-doc.cn

  • @CacheEvict:触发缓存逐出。spring-doc.cn

  • @CachePut:在不干扰方法执行的情况下更新缓存。spring-doc.cn

  • @Caching:重新组合要应用于方法的多个缓存操作。spring-doc.cn

  • @CacheConfig:在类级别共享一些常见的缓存相关设置。spring-doc.cn

注释@Cacheable

顾名思义,您可以使用它来划分可缓存的方法,即结果存储在缓存中的方法,以便在后续的 invocations(具有相同的参数)时,返回缓存中的值时,不会返回 必须实际调用该方法。在最简单的形式中,注解声明 需要与 annotated 方法关联的缓存的名称,如下所示 示例显示:@Cacheablespring-doc.cn

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

在前面的代码段中,该方法与名为 . 每次调用该方法时,都会检查缓存以查看调用是否具有 已运行,无需重复。而在大多数情况下,只有一个 cache 时,注解允许指定多个名称,以便多个 cache 正在使用。在这种情况下,在调用 method — 如果至少命中了一个缓存,则返回关联的值。findBookbooksspring-doc.cn

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

以下示例对具有多个缓存的方法使用:@CacheablefindBookspring-doc.cn

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

默认密钥生成

由于缓存本质上是键值存储,因此每次调用缓存的方法 需要翻译成适合缓存访问的 key。缓存抽象 使用基于以下算法的简单:KeyGeneratorspring-doc.cn

  • 如果未给出参数,则返回 。SimpleKey.EMPTYspring-doc.cn

  • 如果只给出一个参数,则返回该实例。spring-doc.cn

  • 如果给出了多个参数,则返回包含所有参数的 a。SimpleKeyspring-doc.cn

这种方法适用于大多数用例,只要参数具有自然键即可 并实现 valid 和 methods。如果不是这种情况, 你需要改变策略。hashCode()equals()spring-doc.cn

要提供不同的默认密钥生成器,您需要实现该接口。org.springframework.cache.interceptor.KeyGeneratorspring-doc.cn

默认的密钥生成策略随着 Spring 4.0 的发布而改变。早些时候 版本使用 Key 生成策略,对于多个 Key 参数, 只考虑了 of 参数,而不是 。这可能会导致 意外的按键冲突(有关背景信息,请参阅 SPR-10237)。对于此类情况,新版本使用复合键。hashCode()equals()SimpleKeyGeneratorspring-doc.cn

如果您想继续使用之前的 key 策略,您可以配置已弃用的类或创建自定义 基于哈希的实现。org.springframework.cache.interceptor.DefaultKeyGeneratorKeyGeneratorspring-doc.cn

自定义密钥生成声明

由于缓存是泛型的,因此目标方法很可能具有各种签名 这不能轻易地映射到缓存结构之上。这往往变得很明显 当 target 方法具有多个参数,其中只有一些参数适合 缓存(而其余部分仅由 method logic使用)。请考虑以下示例:spring-doc.cn

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

乍一看,虽然这两个论点影响了这本书的找到方式, 它们对缓存没有用处。此外,如果两者中只有一个很重要怎么办 而另一个则不是?booleanspring-doc.cn

对于此类情况,注释允许您指定密钥的生成方式 通过其属性。您可以使用 SpEL 选择 感兴趣的参数(或其嵌套属性)、执行操作,甚至 调用任意方法,而无需编写任何代码或实现任何接口。 这是默认生成器 因为随着代码库的增长,方法的签名往往有很大的不同。虽然 default 策略可能适用于某些方法,但很少适用于所有方法。@Cacheablekeyspring-doc.cn

以下示例使用各种 SPEL 声明(如果您不熟悉 SPEL, 帮自己一个忙,读一读 Spring Expression Language):spring-doc.cn

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

前面的代码片段显示了选择某个参数(其 属性,甚至是任意(静态)方法。spring-doc.cn

如果负责生成密钥的算法过于具体,或者需要 要共享,您可以在操作上定义自定义。为此, 指定要使用的 Bean 实现的名称,如下所示 示例显示:keyGeneratorKeyGeneratorspring-doc.cn

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
和 参数是互斥的,并且是一个操作 指定 both 将导致异常。keykeyGenerator

默认缓存分辨率

缓存抽象使用一个 simple 来检索缓存 使用配置的 .CacheResolverCacheManagerspring-doc.cn

要提供不同的默认缓存解析程序,您需要实现该接口。org.springframework.cache.interceptor.CacheResolverspring-doc.cn

自定义缓存分辨率

默认缓存分辨率非常适合使用 single 的 Random 的 Zip S S 的 RandomCacheManagerspring-doc.cn

对于使用多个缓存管理器的应用程序,您可以设置用于每个操作,如下例所示:cacheManagerspring-doc.cn

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

您也可以以类似于 替换密钥生成。 每个缓存操作都会请求 resolution,让 implementation 实际上,根据运行时参数解析要使用的缓存。以下示例 演示如何指定 :CacheResolverCacheResolverspring-doc.cn

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

从 Spring 4.1 开始,cache 注解的属性不再是 mandatory,因为无论 Comments 的内容如何,都可以提供此特定信息。valueCacheResolverspring-doc.cn

与 and 类似,和 参数是互斥的,并且同时指定 会导致异常,因为 custom 被实现忽略。这可能不是您所期望的。keykeyGeneratorcacheManagercacheResolverCacheManagerCacheResolverspring-doc.cn

同步缓存

在多线程环境中,可能会同时调用 相同的参数(通常在启动时)。默认情况下,缓存抽象不会 锁定任何内容,相同的值可能会被多次计算,从而违背目的 缓存。spring-doc.cn

对于这些特定情况,您可以使用 attribute 来指示底层 cache provider 在计算值时锁定缓存条目。因此, 只有一个线程忙于计算该值,而其他线程被阻塞,直到进入 在缓存中更新。以下示例演示如何使用该属性:syncsyncspring-doc.cn

@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 使用 attribute.sync
这是一项可选功能,您最喜欢的缓存库可能不支持它。 核心框架提供的所有实现都支持它。请参阅 缓存提供商的文档,了解更多详细信息。CacheManager

条件缓存

有时,某个方法可能并不适合一直进行缓存(例如,它可能 取决于给定的参数)。缓存注释通过参数支持此类用例,该参数采用计算结果为 或 的表达式。如果 ,则缓存该方法。如果不是,则它的行为就像方法不是一样 cached (即,无论缓存中的值是什么,每次都会调用该方法 或使用的参数)。例如,仅当 参数的长度短于 32:conditionSpELtruefalsetruenamespring-doc.cn

@Cacheable(cacheNames="book", condition="#name.length() < 32") (1)
public Book findBook(String name)
1 在 上设置条件。@Cacheable

除了参数之外,您还可以使用 parameter 否决 向缓存中添加值。与 不同,表达式被计算 在调用该方法之后。为了扩展前面的例子,也许我们只 想要缓存平装书,如下例所示:conditionunlessconditionunlessspring-doc.cn

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") (1)
public Book findBook(String name)
1 使用 attribute 来阻止 hardbacks。unless

缓存抽象支持返回类型。如果值 存在,它将存储在关联的缓存中。如果值不是 present)将存储在关联的缓存中。 始终引用 业务实体,而不是受支持的包装器,因此可以重写前面的示例 如下:java.util.OptionalOptionalOptionalnull#resultspring-doc.cn

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

请注意,still 引用 而不是 。既然可能是,我们使用 SpEL 的安全导航运算符#resultBookOptional<Book>nullspring-doc.cn

可用的缓存 SPEL 求值上下文

每个表达式都根据专用上下文进行计算。 除了内置参数外,框架还提供了专用的缓存相关 元数据,例如参数名称。下表描述了制造的项目 available to context 中,以便您可以将它们用于键和条件计算:SpELspring-doc.cn

表 1.缓存 SPEL 可用元数据
名字 位置 描述

methodNamespring-doc.cn

根对象spring-doc.cn

正在调用的方法的名称spring-doc.cn

#root.methodNamespring-doc.cn

methodspring-doc.cn

根对象spring-doc.cn

正在调用的方法spring-doc.cn

#root.method.namespring-doc.cn

targetspring-doc.cn

根对象spring-doc.cn

正在调用的目标对象spring-doc.cn

#root.targetspring-doc.cn

targetClassspring-doc.cn

根对象spring-doc.cn

正在调用的目标的类spring-doc.cn

#root.targetClassspring-doc.cn

argsspring-doc.cn

根对象spring-doc.cn

用于调用目标的参数(作为数组)spring-doc.cn

#root.args[0]spring-doc.cn

cachesspring-doc.cn

根对象spring-doc.cn

运行当前方法所针对的缓存的集合spring-doc.cn

#root.caches[0].namespring-doc.cn

参数名称spring-doc.cn

评估上下文spring-doc.cn

任何方法参数的名称。如果名称不可用 (可能是由于没有调试信息),参数名称也可以在 where 代表参数索引(从 开始)下找到。#a<#arg>#arg0spring-doc.cn

#ibanor (您也可以使用 or 表示法作为别名)。#a0#p0#p<#arg>spring-doc.cn

resultspring-doc.cn

评估上下文spring-doc.cn

方法调用的结果(要缓存的值)。仅在表达式、表达式(计算 )或表达式(when is )中可用。对于支持的包装器(如 ),是指实际对象,而不是包装器。unlesscache putkeycache evictbeforeInvocationfalseOptional#resultspring-doc.cn

#resultspring-doc.cn

注释@CachePut

当需要更新缓存而不干扰方法执行时, 您可以使用 Annotation.也就是说,该方法始终被调用,并且其 result 被放入缓存中(根据选项)。它支持 与 和 相同的选项应用于缓存填充,而不是 方法流程优化。以下示例使用注释:@CachePut@CachePut@Cacheable@CachePutspring-doc.cn

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
在相同的方法上使用 和 annotation 通常是 强烈反对,因为他们的行为不同。虽然后者会导致 方法调用,前者强制在 运行缓存更新。这会导致意外行为,并且除了 的特定极端情况(例如,具有将它们从每个 other),则应避免使用此类声明。另请注意,这些条件不应依赖于 在 result 对象(即变量)上,因为这些是预先验证的 确认排除项。@CachePut@Cacheable#result

注释@CacheEvict

缓存抽象不仅允许填充缓存存储,还允许逐出。 此过程可用于从缓存中删除过时或未使用的数据。与 相反,划分执行缓存的方法 逐出(即充当从缓存中删除数据的触发器的方法)。 与它的同级类似,需要指定一个或多个缓存 ,则允许自定义缓存和密钥解析或 条件,并具有一个额外的参数 () 指示是否需要执行缓存范围的逐出 而不仅仅是条目驱逐(基于键)。以下示例驱逐 缓存中的所有条目:@Cacheable@CacheEvict@CacheEvictallEntriesbooksspring-doc.cn

@CacheEvict(cacheNames="books", allEntries=true) (1)
public void loadBooks(InputStream batch)
1 使用 attribute 从缓存中逐出所有条目。allEntries

当需要清除整个缓存区域时,此选项会派上用场。 而不是逐出每个条目(这将花费很长时间,因为它效率低下), 如前面的示例所示,所有条目都在一个操作中删除。 请注意,框架会忽略此方案中指定的任何键,因为它不适用 (整个缓存被驱逐,而不仅仅是一个条目)。spring-doc.cn

您还可以指示驱逐是应该在 (默认) 之后还是之前进行 该方法使用 属性 调用。前者提供 与其他 Comments 的语义相同:方法成功完成后, 对缓存运行操作(在本例中为 Eviction)。如果该方法没有 run(因为它可能被缓存)或引发异常,则不会发生驱逐。 后者 () 导致驱逐始终发生在 方法。这在不需要绑定驱逐的情况下非常有用 到方法结果。beforeInvocationbeforeInvocation=truespring-doc.cn

请注意,方法可以与 - 一起使用,因为这些方法充当 trigger,则返回值将被忽略(因为它们不与缓存交互)。这是 不是将数据添加到缓存或更新缓存中的数据的情况 因此,需要一个结果。void@CacheEvict@Cacheablespring-doc.cn

注释@Caching

有时,需要指定多个相同类型的 Comments(例如 or ),例如,因为 condition 或 key expression 在不同缓存之间是不同的。 允许在同一方法上使用多个嵌套的 、 和 注释。 以下示例使用两个注释:@CacheEvict@CachePut@Caching@Cacheable@CachePut@CacheEvict@CacheEvictspring-doc.cn

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

注释@CacheConfig

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

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

	@Cacheable
	public Book findBook(ISBN isbn) {...}
}
1 用于设置缓存的名称。@CacheConfig

@CacheConfig是允许共享缓存名称的类级注解, 自定义 、 自定义 和 自定义 。 将此注释放在类上不会打开任何缓存操作。KeyGeneratorCacheManagerCacheResolverspring-doc.cn

操作级自定义设置始终覆盖在 上设置的自定义项。 因此,这为每个缓存操作提供了三个级别的自定义:@CacheConfigspring-doc.cn

特定于提供程序的设置通常在 Bean 上可用, 例如 on .这些实际上也是全球性的。CacheManagerCaffeineCacheManager

启用缓存注释

请务必注意,即使声明缓存注释不会 自动触发他们的操作 - 就像 Spring 中的许多事情一样,该功能必须 声明式启用(这意味着如果您怀疑缓存是罪魁祸首,您可以 通过仅删除 1 个 configuration 行而不是 您的代码)。spring-doc.cn

要启用缓存注释,请将注释添加到您的类之一:@EnableCaching@Configurationspring-doc.cn

@Configuration
@EnableCaching
public class AppConfig {

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

或者,对于 XML 配置,您可以使用以下元素:cache:annotation-drivenspring-doc.cn

<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@EnableCachingspring-doc.cn

处理缓存 annotation 的默认通知模式是 ,它允许 仅用于通过代理拦截呼叫。同一类中的本地呼叫 不能以这种方式被拦截。对于更高级的拦截模式,请考虑 切换到与编译时或加载时编织相结合的模式。proxyaspectj
有关高级自定义 (使用 Java 配置) 的更多详细信息,这些自定义 需要实现 ,请参阅 JavaDoc.CachingConfigurer
表 2.缓存注释设置
XML 属性 注释属性 违约 描述

cache-managerspring-doc.cn

N/A (参见 CachingConfigurer javadoc)spring-doc.cn

cacheManagerspring-doc.cn

要使用的缓存管理器的名称。默认值在 behind 初始化 具有此缓存管理器(或如果未设置)的场景。了解更多 对缓存分辨率进行细粒度管理,请考虑设置 'cache-resolver' 属性。CacheResolvercacheManagerspring-doc.cn

cache-resolverspring-doc.cn

N/A (参见 CachingConfigurer javadoc)spring-doc.cn

A 使用配置的 .SimpleCacheResolvercacheManagerspring-doc.cn

用于解析后备缓存的CacheResolver的 Bean 名称。 此属性不是必需的,只需指定为 'cache-manager' 属性。spring-doc.cn

key-generatorspring-doc.cn

N/A (参见 CachingConfigurer javadoc)spring-doc.cn

SimpleKeyGeneratorspring-doc.cn

要使用的自定义密钥生成器的名称。spring-doc.cn

error-handlerspring-doc.cn

N/A (参见 CachingConfigurer javadoc)spring-doc.cn

SimpleCacheErrorHandlerspring-doc.cn

要使用的自定义缓存错误处理程序的名称。默认情况下,在 与缓存相关的操作将退回到客户端。spring-doc.cn

modespring-doc.cn

modespring-doc.cn

proxyspring-doc.cn

默认模式()处理带注释的 bean 以使用 Spring 的 AOP 进行代理 框架(如前所述,遵循代理语义,应用于方法调用 仅通过代理进入)。相反,替代模式 () 将 影响了具有 Spring 的 AspectJ 缓存方面的类,修改了目标类的字节 code 应用于任何类型的方法调用。AspectJ 编织需要在 Classpath 中启用加载时编织(或编译时编织)。(有关如何设置的详细信息,请参阅 Spring 配置 加载时编织。proxyaspectjspring-aspects.jarspring-doc.cn

proxy-target-classspring-doc.cn

proxyTargetClassspring-doc.cn

falsespring-doc.cn

仅适用于代理模式。控制为什么类型的缓存代理创建 使用 or 注释注释的类。如果该属性设置为 ,则会创建基于类的代理。 如果是或省略属性,则为标准 JDK 创建基于接口的代理。(有关不同代理类型的详细检查,请参阅 代理机制@Cacheable@CacheEvictproxy-target-classtrueproxy-target-classfalsespring-doc.cn

orderspring-doc.cn

orderspring-doc.cn

Ordered.LOWEST_PRECEDENCEspring-doc.cn

定义应用于用 或 注释的 bean 的缓存通知的顺序。(有关 订购 AOP 建议,请参阅 建议订购。 没有指定的 Sequences 意味着 AOP 子系统确定通知的 Sequences。@Cacheable@CacheEvictspring-doc.cn

<cache:annotation-driven/>仅在定义 bean 的同一应用程序上下文中查找 。这意味着, 如果输入 a for a ,则它仅检查控制器中的 bean,而不检查服务中的 bean。 有关更多信息,请参阅 MVC 部分@Cacheable/@CachePut/@CacheEvict/@Caching<cache:annotation-driven/>WebApplicationContextDispatcherServlet
方法可见性和缓存注释

使用代理时,应仅将缓存注释应用于具有 公众可见性。如果您注释 protected、private 或 package-visible 方法 使用这些 Annotations 时,不会引发错误,但 Annotated 方法不会显示 配置的缓存设置。考虑使用 AspectJ(参见本节的其余部分) 如果你需要注释非公共方法,因为它会改变字节码本身。spring-doc.cn

Spring 建议您只注释具体的类(以及 concrete 的方法 类)替换为 Comments,而不是 Annotating 接口。 您当然可以在接口(或接口 方法),但这仅在您使用代理模式 () 时有效。如果您使用 基于 Weaving-based aspect () 时,缓存设置无法识别 由 Weaving Infrastructure 提供的接口级声明。@Cache*@Cache*mode="proxy"mode="aspectj"
在代理模式(默认)下,只有通过 proxy 被拦截。这意味着自调用(实际上是 target 对象调用目标对象的另一个方法)不会导致实际的 缓存,即使调用的方法标有 .考虑 在这种情况下使用 THE MODE。此外,代理必须完全初始化为 提供预期的行为,因此您不应在 初始化代码(即 )。@Cacheableaspectj@PostConstruct

使用自定义注释

自定义注释和 AspectJ

此功能仅适用于基于代理的方法,但可以启用 使用 AspectJ 进行一些额外的努力。spring-doc.cn

该模块仅为标准注释定义一个方面。 如果你已经定义了自己的注解,你还需要为 那些。查看示例。spring-aspectsAnnotationCacheAspectspring-doc.cn

缓存抽象允许您使用自己的 Comments 来识别方法。 触发缓存填充或驱逐。作为模板机制,这非常方便, 因为它消除了复制缓存注释声明的需要,即 如果指定了 key 或 condition,或者 foreign imports () 不允许出现在您的代码库中。与其他人类似 的 stereotype 注解中,你可以 使用 、 、 和 作为元注释(即 可以注释其他注释)。在以下示例中,我们将通用声明替换为我们自己的自定义注释:org.springframework@Cacheable@CachePut@CacheEvict@CacheConfig@Cacheablespring-doc.cn

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

在前面的示例中,我们定义了自己的注解 它本身用 .现在我们可以替换以下代码:SlowService@Cacheablespring-doc.cn

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

以下示例显示了自定义注释,我们可以用它来替换 前面的代码:spring-doc.cn

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

即使 is 不是 Spring 注解,容器也会自动选择 up 它的声明,并理解它的含义。请注意,如前所述,需要启用 Comments 驱动的行为。@SlowServicespring-doc.cn