Spring Framework 参考文档(声明式基于注解的缓存)

声明式基于注解的缓存

对于缓存声明,Spring的缓存抽象提供了一组Java注解:html

  • @Cacheable:触发缓存人口。
  • @CacheEvict:触发缓存驱逐。
  • @CachePut:在不影响方法执行的状况下更新缓存。
  • @Caching:从新组合要应用于方法的多个缓存操做。
  • @CacheConfig:在类级别上共享一些常见的缓存相关设置。

@Cacheable注解

顾名思义,你可使用@Cacheable来划分可缓存的方法 — 也就是说,将结果存储在缓存中的方法,以便在后续调用(具备相同的参数)时返回缓存中的值,而没必要实际执行该方法。在其最简单的形式中,注解声明须要与带注解的方法相关联的缓存的名称,以下面的示例所示:java

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

在前面的代码片断中,findBook方法与名为books的缓存相关联,每次调用该方法时,都会检查缓存,以查看是否已经执行了调用,而且不须要重复调用。虽然在大多数状况下,只声明一个缓存,可是注解容许指定多个名称,以便使用多个缓存。在本例中,在执行方法以前检查每一个缓存 — 若是至少命中一个缓存,则返回关联的值。算法

全部其余不包含该值的缓存也会被更新,即便缓存的方法并无实际执行。

下面的示例在findBook方法上使用@Cacheablespring

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

默认键生成

因为缓存本质上是键值存储,所以每次对缓存方法的调用都须要转换为适合缓存访问的键,缓存抽象使用基于如下算法的简单KeyGeneratorsegmentfault

  • 若是没有提供参数,则返回SimpleKey.EMPTY
  • 若是只给出一个参数,则返回该实例。
  • 若是给定多个参数,则返回包含全部参数的SimpleKey

这种方法适用于大多数用例,只要参数具备天然键并实现有效的hashCode()equals()方法,若是不是这样,你须要改变策略。api

要提供不一样的默认键生成器,你须要实现org.springframework.cache.interceptor.KeyGenerator接口。数组

默认的键生成策略随着Spring 4.0的发布而改变,Spring的早期版本使用的键生成策略,对于多个键参数,只考虑参数的 hashCode(),而不考虑 equals(),这可能会致使意想不到的键冲突(有关背景,请参见 SPR-10237),新的 SimpleKeyGenerator为这些场景使用复合键。

若是你想继续使用前面的键策略,能够配置已废弃的org.springframework.cache.interceptor.DefaultKeyGenerator类,或者建立一个基于散列的自定义KeyGenerator实现。缓存

自定义键生成声明

因为缓存是通用的,所以目标方法极可能具备各类签名,这些签名没法轻松映射到缓存结构之上,当目标方法有多个参数时,这每每会变得明显,其中只有一些参数适用于缓存(其他参数仅由方法逻辑使用),请考虑如下示例:安全

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

乍一看,虽然两个boolean参数影响了书的发现方式,但它们对缓存没有用处,若是两个中只有一个重要而另外一个不重要怎么办?多线程

对于这种状况,@Cacheable注解容许你经过其key属性指定键的生成方式,你可使用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)

前面的代码片断显示了选择某个参数、其属性之1、甚至是任意(静态)方法是多么容易。

若是负责生成键的算法太具体或须要共享,则能够在操做上定义自定义keyGenerator,为此,请指定要使用的KeyGenerator bean实现的名称,如如下示例所示:

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
keykeyGenerator参数是互斥的,一个操做指定二者会致使异常。

默认的缓存解析

缓存抽象使用简单的CacheResolver,它使用配置的CacheManager检索在操做级别定义的缓存。

要提供不一样的默认缓存解析器,你须要实现org.springframework.cache.interceptor.CacheResolver接口。

自定义缓存解析

默认缓存解析很是适合使用单个CacheManager而且没有复杂缓存解析要求的应用程序。

对于使用多个缓存管理器的应用程序,能够将cacheManager设置为用于每一个操做,如如下示例所示:

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

你也能够彻底替换CacheResolver,方式相似于替换键生成,为每一个缓存操做请求解析,让实现实际上根据运行时参数解析要使用的缓存,如下示例显示如何指定CacheResolver

@Cacheable(cacheResolver="runtimeCacheResolver") 
public Book findBook(ISBN isbn) {...}
从Spring 4.1开始,缓存注解的 value属性再也不是必需的,由于不管注解的内容如何,​​ CacheResolver均可以提供此特定信息。

keykeyGenerator相似,cacheManagercacheResolver参数是互斥的,指定这二者的操做会致使异常,由于CacheResolver实现忽略了自定义CacheManager,这可能不是你所指望的。

同步缓存

在多线程环境中,可能会为同一参数同时调用某些操做(一般在启动时),默认状况下,缓存抽象不会锁定任何内容,而且可能会屡次计算相同的值,从而没法实现缓存。

对于这些特定状况,你可使用sync属性指示底层缓存提供程序在计算值时锁定缓存条目,所以,只有一个线程忙于计算该值,而其余线程则被阻塞,直到该条目在缓存中更新为止,如下示例显示了如何使用sync属性:

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

条件缓存

有时,方法可能不适合一直缓存(例如,它可能取决于给定的参数),缓存注解经过condition参数支持此类功能,该参数采用被评估为truefalseSpEL表达式,若是为true,则缓存该方法,若是没有,它的行为就好像该方法没有被缓存(也就是说,不管缓存中的值是什么,或者使用了什么参数,每次都执行该方法)。例如,仅当参数name的长度小于32时,才会缓存如下方法:

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

condition参数外,还可使用unless参数否决向缓存添加值,与condition不一样,unless表达式在调用该方法后进行评估,要扩展上一个示例,也许咱们只想缓存平装书,以下例所示:

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

缓存抽象支持java.util.Optional,仅在其存在时将其内容用做缓存值,#result老是引用业务实体而从不支持包装器,所以前面的示例能够重写以下:

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

请注意,result仍然引用Book而不是Optional,因为它可能为null,咱们应该使用安全导航操做符。

可用的缓存SpEL评估上下文

每一个SpEL表达式都针对专用context进行评估,除了内置参数以外,框架还提供专用的与缓存相关的元数据,例如参数名称。下表描述了上下文可用的项目,以便你能够将它们用于键和条件计算:

名称 位置 描述 示例
methodName 根对象 要调用的方法的名称 #root.methodName
method 根对象 正在调用的方法 #root.method.name
target 根对象 正在调用的目标对象 #root.target
targetClass 根对象 正在调用的目标的类 #root.targetClass
args 根对象 用于调用目标的参数(做为数组) #root.args[0]
caches 根对象 执行当前方法的高速缓存的集合 #root.caches[0].name
参数名称 评估上下文 任何方法参数的名称;
若是名称不可用(可能因为没有调试信息),
参数名称也能够在#a<#arg>下得到。
其中#arg表明参数索引(从0开始)。
#iban#a0
(你也可使用#p0
#p<#arg>表示法做为别名)
result 评估上下文 方法调用的结果(要缓存的值)
只能在unless表达式、缓存放置表达式(计算键)
或缓存逐出表达式(当beforeInvocationfalse
时)中使用
对于受支持的包装器(例如Optional),
#result引用实际的对象,而不是包装器。
#result

@CachePut注解

当须要更新缓存而不干扰方法执行时,可使用@CachePut注解,也就是说,始终执行该方法,并将其结果放入缓存中(根据@CachePut选项)。它支持与@Cacheable相同的选项,应该用于缓存填充而不是方法流优化,如下示例使用@CachePut注解:

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
一般强烈建议不要在同一方法上使用 @CachePut@Cacheable注解,由于它们有不一样的行为,虽而后者致使经过使用缓存跳过方法执行,但前者强制执行以执行缓存更新。这将致使意想不到的行为,除了特定的状况外(例如注解具备将它们彼此排除的条件)以外,应避免此类声明。还请注意,这些条件不该该依赖于 result对象(即 #result变量),由于这些都是预先验证以确认排除的。

@CacheEvict注解

缓存抽象不只容许缓存存储的填充,还容许驱逐,此过程对于从缓存中删除陈旧或未使用的数据很是有用。与@Cacheable相反,@CacheEvict划分了执行缓存逐出的方法(即,用做从缓存中删除数据的触发器的方法)。@CacheEvict须要指定受操做影响的一个或多个缓存,容许指定自定义缓存和键解析或条件,并具备一个额外的参数(allEntries),指示是否须要执行缓存范围的驱逐而不只仅是条目驱逐(基于键),如下示例逐出books缓存中的全部条目:

@CacheEvict(cacheNames="books", allEntries=true) 
public void loadBooks(InputStream batch)

当须要清除整个缓存区域时,此选项会派上用场,而不是逐出每一个条目(这将花费很长时间,由于它是低效的),全部条目在一个操做中被移除,如前面的示例所示。请注意,框架会忽略此方案中指定的任何键,由于它不适用(整个缓存被驱逐,而不只仅是一个条目)。

你还能够经过使用beforeInvocation属性指示驱逐是在方法执行以后(默认)仍是在方法执行以前进行的,前者提供与其余注解相同的语义:方法成功完成后,将执行缓存上的操做(在本例中为驱逐),若是方法未执行(由于它可能被缓存)或抛出异常,则不会发生驱逐。后者(beforeInvocation=true)致使驱逐始终在调用方法以前发生,这在驱逐不须要与方法结果相关联的状况下很是有用。

请注意,void方法能够与@CacheEvict一块儿使用 — 由于方法充当触发器,返回值将被忽略(由于它们不与缓存交互),这不是@Cacheable的状况,它将数据添加或更新到缓存中,所以须要结果。

@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") 
public class BookRepositoryImpl implements BookRepository {

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

@CacheConfig是一个类级注解,容许共享缓存名称、自定义KeyGenerator、自定义CacheManager和自定义CacheResolver,将此注解放在类上不会打开任何缓存操做。

操做级别自定义始终覆盖@CacheConfig上的自定义集,所以,这为每一个缓存操做提供了三个级别的自定义:

  • 全局配置,可用于CacheManagerKeyGenerator
  • 在类级别,使用@CacheConfig
  • 在操做级别。

启用缓存注解

请务必注意,即便声明缓存注解也不会自动触发其操做 — 与Spring中的许多功能同样,该特性必须以声明方式启用(这意味着若是你怀疑缓存是罪魁祸首,你能够经过仅删除一个配置行而不是代码中的全部注解来禁用它)。

要启用缓存注解,请将注解@EnableCaching添加到@Configuration类之一:

@Configuration
@EnableCaching
public class AppConfig {
}

或者,对于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/>
</beans>

cache:annotation-driven元素和@EnableCaching注解都容许你指定各类选项,这些选项影响方式经过AOP将缓存行为添加到应用程序,该配置与@Transactional的配置有意相似。

处理缓存注解的默认建议模式是 proxy,它容许仅经过代理拦截调用,同一类中的本地调用不能以这种方式截获,对于更高级的拦截模式,请考虑结合编译时或加载时编织切换到 aspectj模式。
有关实现 CachingConfigurer所需的高级自定义(使用Java配置)的更多详细信息,请参阅 javadoc
缓存注解设置
XML属性 注解属性 默认 描述
cache-manager N/A(参见CachingConfigurer javadoc cacheManager 要使用的缓存管理器的名称;
使用此缓存管理器(若是未设置则为cacheManager)在后台初始化默认的CacheResolver
要得到更精细的缓存解析管理,请考虑设置“cache-resolver”属性。
cache-resolver N/A(参见CachingConfigurer javadoc 使用配置的cacheManagerSimpleCacheResolver 用于解析后备缓存的CacheResolver的bean名称;
此属性不是必需的,只须要指定为“cache-manager”属性的替代。
key-generator N/A(参见CachingConfigurer javadoc SimpleKeyGenerator 要使用的自定义键生成器的名称。
error-handler N/A(参见CachingConfigurer javadoc SimpleCacheErrorHandler 要使用的自定义缓存错误处理程序的名称;
默认状况下,在缓存相关操做期间抛出的任何异常都会在客户端返回。
mode mode proxy 默认模式(proxy)处理要使用Spring的AOP框架代理的带注解bean(遵循代理语义,如前所述,仅适用于经过代理进入的方法调用);
替代模式(aspectj)用Spring的AspectJ缓存切面编织受影响的类,修改目标类字节码以应用于任何类型的方法调用;
AspectJ编织须要在类路径中使用spring-aspects.jar以及启用加载时编织(或编译时编织);
(有关如何设置加载时编织的详细信息,请参阅Spring配置)。
proxy-target-class proxyTargetClass false 仅适用于代理模式;
控制为使用@Cacheable@CacheEvict注解注解的类建立哪一种类型的缓存代理;
若是proxy-target-class属性设置为true,则建立基于类的代理;
若是proxy-target-classfalse或者省略了该属性,则会建立基于标准JDK接口的代理;
(有关不一样代理类型的详细解释,请参阅代理机制)。
order order Ordered.LOWEST_PRECEDENCE 定义应用于使用@Cacheable@CacheEvict注解的bean的缓存建议的顺序;
(有关排序AOP建议相关规则的更多信息,请参阅建议排序);
没有指定的排序意味着AOP子系统肯定建议的顺序。
<cache:annotation-driven/>查找 @Cacheable/@CachePut/@CacheEvict/@Caching仅在定义它的同一应用程序上下文中的bean上进行缓存,这意味着,若是你在 WebApplicationContext中为 DispatcherServlet放置 <cache:annotation-driven/>,它只会在你的控制器中检查bean,而不是你的服务,有关更多信息,请参阅MVC部分。

方法可见性和缓存注解

使用代理时,应仅将缓存注解应用于具备公共可见性的方法,若是使用这些注解来注解protectedprivate或包可见方法,不会引起错误,但带注解的方法不会显示已配置的缓存设置。若是须要注解非公共方法,请考虑使用AspectJ(请参阅本节的其他部分),由于它会更改字节码自己。

Spring建议只使用 @Cache*注解来注解具体类(以及具体类的方法),而不是注解接口,你固然能够将 @Cache*注解放在接口(或接口方法)上,但这只能在你使用基于接口的代理时按预期工做。Java注解不是从接口继承的事实意味着,若是你使用基于类的代理( proxy-target-class="true")或基于编织的切面( mode="aspectj"),代理和编织基础设施没法识别缓存设置,而且该对象未包装在缓存代理中。
在代理模式(默认)下,只拦截经过代理进入的外部方法调用,这意味着自调用(其实是目标对象中调用目标对象的另外一个方法的方法)在运行时不会致使实际的缓存,即便调用的方法用 @Cacheable标记,在这种状况下,请考虑使用 aspectj模式。此外,必须彻底初始化代理以提供预期的行为,所以你不该该在初始化代码(即 @PostConstruct)中依赖此功能。

使用自定义注解

自定义注解和AspectJ

此特性仅适用于基于代理的方法,但能够经过使用AspectJ进行一些额外的工做。
spring-aspects模块仅定义标准注解的切面,若是你已定义本身的注解,则还须要为这些注解定义切面。

缓存抽象容许你使用本身的注解来标识触发缓存填充或驱逐的方法,这做为模板机制很是方便,由于它消除了重复缓存注解声明的须要,这在指定了键或条件或者代码库中不容许外部导入(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)

尽管@SlowService不是Spring注解,但容器会在运行时自动获取其声明并理解其含义,请注意,如前所述,须要启用annotation-driven的行为。


上一篇:理解缓存抽象

相关文章
相关标签/搜索