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

基于声明性注释的缓存

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

@Cacheable注解

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

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

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

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

以下示例使用@CacheablefindBook方法中:spring-doc.cadn.net.cn

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

默认密钥生成

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

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

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

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

如果您想继续使用之前的密钥策略,可以将已弃用的org.springframework.cache.interceptor.DefaultKeyGenerator类或创建自定义 基于哈希KeyGenerator实现。spring-doc.cadn.net.cn

自定义密钥生成声明

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

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

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

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

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

如果负责生成密钥的算法过于具体,或者需要 要共享,您可以定义自定义keyGenerator在行动中。为此, 指定KeyGenerator要使用的 bean 实现,如下所示 示例显示:spring-doc.cadn.net.cn

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
keykeyGeneratorparameters 是互斥的,并且 指定 both 将导致异常。

默认缓存分辨率

缓存抽象使用一个简单的CacheResolver检索缓存 在作级别使用配置的CacheManager.spring-doc.cadn.net.cn

要提供不同的默认缓存解析器,您需要实现org.springframework.cache.interceptor.CacheResolver接口。spring-doc.cadn.net.cn

自定义缓存分辨率

默认缓存分辨率非常适合使用 单CacheManager并且没有复杂的缓存分辨率要求。spring-doc.cadn.net.cn

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

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

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

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

从 Spring 4.1 开始,valuecache 注解的属性不再是 mandre,因为此特定信息可由CacheResolver无论批注的内容如何。spring-doc.cadn.net.cn

keykeyGeneratorcacheManagercacheResolver参数是互斥的,并且指定 导致异常,作为自定义CacheManagerCacheResolver实现。这可能不是您所期望的。spring-doc.cadn.net.cn

同步缓存

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

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

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

条件缓存

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

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

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

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

缓存抽象支持java.util.Optionalreturn 类型。如果Optional价值 存在,它将存储在关联的缓存中。如果Optionalvalue 不是 目前null将存储在关联的缓存中。#result始终引用 业务实体,而不是受支持的包装器,因此可以重写前面的示例 如下:spring-doc.cadn.net.cn

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

请注意,#result仍然指Book而不是Optional<Book>.既然可能是null,我们使用 SpEL 的安全导航运算符spring-doc.cadn.net.cn

可用的缓存 SPEL 求值上下文

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

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

methodNamespring-doc.cadn.net.cn

根对象spring-doc.cadn.net.cn

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

#root.methodNamespring-doc.cadn.net.cn

methodspring-doc.cadn.net.cn

根对象spring-doc.cadn.net.cn

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

#root.method.namespring-doc.cadn.net.cn

targetspring-doc.cadn.net.cn

根对象spring-doc.cadn.net.cn

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

#root.targetspring-doc.cadn.net.cn

targetClassspring-doc.cadn.net.cn

根对象spring-doc.cadn.net.cn

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

#root.targetClassspring-doc.cadn.net.cn

argsspring-doc.cadn.net.cn

根对象spring-doc.cadn.net.cn

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

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

cachesspring-doc.cadn.net.cn

根对象spring-doc.cadn.net.cn

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

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

参数名称spring-doc.cadn.net.cn

评估上下文spring-doc.cadn.net.cn

任何方法参数的名称。如果名称不可用 (可能是由于没有调试信息),参数名称也可以在#a<#arg>哪里#arg代表参数索引(从0).spring-doc.cadn.net.cn

#iban#a0(您还可以使用#p0#p<#arg>表示法作为别名)。spring-doc.cadn.net.cn

resultspring-doc.cadn.net.cn

评估上下文spring-doc.cadn.net.cn

方法调用的结果(要缓存的值)。仅适用于unless表达 式cache put表达式(要计算key) 或cache evict表达式(当beforeInvocationfalse).对于支持的包装器(例如Optional),#result引用实际对象,而不是包装器。spring-doc.cadn.net.cn

#resultspring-doc.cadn.net.cn

@CachePut注解

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

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

@CacheEvict注解

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

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

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

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

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

@Caching注解

有时,同一类型的多个注解(例如@CacheEvict@CachePut) — 例如,因为 condition 或 key expression 在不同缓存之间是不同的。@Caching允许多个嵌套@Cacheable,@CachePut@CacheEvictannotations 可以使用相同的方法。 以下示例使用两个@CacheEvict附注:spring-doc.cadn.net.cn

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

@CacheConfig注解

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

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

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

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

作级自定义始终覆盖@CacheConfig. 因此,这为每个缓存作提供了三个级别的自定义:spring-doc.cadn.net.cn

提供程序特定的设置通常位于CacheManager豆 例如,在CaffeineCacheManager.这些实际上也是全球性的。

启用缓存注释

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

要启用缓存注释,请添加注释@EnableCaching到你的@Configuration类:spring-doc.cadn.net.cn

@Configuration
@EnableCaching
public class AppConfig {

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

或者,对于 XML 配置,您可以使用cache:annotation-driven元素:spring-doc.cadn.net.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>

cache:annotation-driven元素和@EnableCachingannotation 允许您 指定影响缓存行为添加到 通过 AOP 申请。该配置有意与@Transactional.spring-doc.cadn.net.cn

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

cache-managerspring-doc.cadn.net.cn

N/A(请参阅CachingConfigurerjavadoc)spring-doc.cadn.net.cn

cacheManagerspring-doc.cadn.net.cn

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

cache-resolverspring-doc.cadn.net.cn

N/A(请参阅CachingConfigurerjavadoc)spring-doc.cadn.net.cn

一个SimpleCacheResolver使用配置的cacheManager.spring-doc.cadn.net.cn

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

key-generatorspring-doc.cadn.net.cn

N/A(请参阅CachingConfigurerjavadoc)spring-doc.cadn.net.cn

SimpleKeyGeneratorspring-doc.cadn.net.cn

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

error-handlerspring-doc.cadn.net.cn

N/A(请参阅CachingConfigurerjavadoc)spring-doc.cadn.net.cn

SimpleCacheErrorHandlerspring-doc.cadn.net.cn

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

modespring-doc.cadn.net.cn

modespring-doc.cadn.net.cn

proxyspring-doc.cadn.net.cn

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

proxy-target-classspring-doc.cadn.net.cn

proxyTargetClassspring-doc.cadn.net.cn

falsespring-doc.cadn.net.cn

仅适用于代理模式。控制为什么类型的缓存代理创建 类中带有@Cacheable@CacheEvict附注。如果proxy-target-class属性设置为true,将创建基于类的代理。 如果proxy-target-classfalse或者,如果省略了该属性,则使用标准 JDK 创建基于接口的代理。(有关不同代理类型的详细检查,请参阅 代理机制spring-doc.cadn.net.cn

orderspring-doc.cadn.net.cn

orderspring-doc.cadn.net.cn

Ordered.LOWEST_PRECEDENCEspring-doc.cadn.net.cn

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

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

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

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

使用自定义注释

自定义注释和 AspectJ

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

spring-aspectsmodule 仅定义标准 annotation 的 aspect 。 如果你已经定义了自己的注解,你还需要为 那些。检查AnnotationCacheAspect例如。spring-doc.cadn.net.cn

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

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

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

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

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

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

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


APP信息