Spring Framework 中文文档

4.3.21.RELEASE

36. 缓存抽象

36.1 简介

从 version 3.1 开始,Spring Framework 支持透明地将缓存添加到现有的 Spring application 中。与交易支持类似,缓存抽象允许一致使用各种缓存解决方案,而对 code 的影响最小。

从 Spring 4.1 开始,在JSR-107 注释和更多自定义选项的支持下,缓存抽象得到了显着改进。

36.2 了解缓存抽象


缓存与缓冲区

术语“缓冲区”和“缓存”倾向于可互换使用;但请注意它们代表不同的东西。传统上,缓冲区用作快速和慢速实体之间的数据的中间临时 store。由于一方必须等待另一方影响 performance,缓冲区通过允许整个数据块一次移动而不是小块来缓解这种情况。数据仅从缓冲区写入和读取一次。此外,缓冲区对于至少知道它的一方是可见的。

另一方面,隐藏的缓存是隐藏的,并且任何一方都不知道缓存 occurs.It 也改善了 performance,但是通过允许以快速方式多次读取相同的数据来实现。

可以找到两者之间差异的进一步解释这里


从根本上说,抽象将缓存应用于 Java 方法,从而减少了基于缓存中可用信息的执行次数。也就是说,每个 time 调用一个目标方法,抽象将应用一个缓存行为,检查该方法是否已经为给定的 arguments 执行。如果有,则返回缓存的结果而不必执行实际的方法;如果没有,则执行方法,将结果缓存并返回给用户,以便在调用该方法的下一个 time 时,返回缓存的结果。这样,昂贵的方法(无论是 CPU 还是 IO 绑定)只能对给定的一组参数执行一次,并且重用结果而不必再次实际执行该方法。缓存逻辑是透明应用的,不会对调用者造成任何干扰。

显然,这种方法仅适用于保证为给定输入(或 arguments)返回相同输出(结果)的方法,无论它执行多少次。

其他 cache-related 操作由抽象提供,例如更新缓存内容或删除所有条目之一的能力。如果缓存处理在 application 过程中可能发生变化的数据,这些非常有用。

就像 Spring Framework 中的其他服务一样,缓存服务是一个抽象(不是缓存实现),需要使用实际存储来存储缓存数据 - 也就是说,抽象使开发人员不必编写缓存逻辑但不提供实际的 stores。这种抽象由org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口实现。

这个抽象有一些实现开箱即用:基于 JDK java.util.concurrent.ConcurrentMap的缓存,Ehcache 2.x,Gemfire 缓存,咖啡因Guava 缓存和 JSR-107 兼容缓存(e.g. Ehcache 3.x)。有关插入其他缓存 stores/providers 的更多信息,请参见第 36.7 节,“Plugging-in 不同 back-end 缓存”

缓存抽象没有 multi-threaded 和 multi-process 环境的特殊处理,因为 features 由 cache implementation 处理。 。

如果您有 multi-process 环境(i.e.在多个节点上部署了 application),则需要相应地配置缓存提供程序。根据您的使用情况,多个节点上相同数据的副本可能已足够,但如果您在 application 过程中更改数据,则可能需要启用其他传播机制。

缓存特定的 item 直接等同于程序化缓存交互中发现的典型 get-if-not-found-then - proceed-and-put-eventually code 块:没有应用锁定,并且多个线程可能会尝试同时加载相同的 item。这同样适用于驱逐:如果多个线程试图同时更新或驱逐数据,则可以使用陈旧数据。某些缓存提供程序在该区域中提供高级 features,请参阅您正在使用的缓存提供程序的文档以获取更多详细信息。

要使用缓存抽象,开发人员需要处理两个方面:

  • 缓存声明 - 确定需要缓存的方法及其 policy

  • cache configuration - 存储和读取数据的后备缓存

36.3 声明式 annotation-based 缓存

对于缓存声明,抽象提供了一组 Java annotations:

  • @Cacheable触发缓存填充

  • @CacheEvict触发缓存驱逐

  • @CachePut更新缓存而不会干扰方法执行

  • @Caching重新组合要在方法上应用的多个缓存操作

  • @CacheConfig在 class-level 分享一些 common cache-related 设置

让我们仔细看看每个 annotation:

36.3.1 @Cacheable annotation

正如 name 所暗示的那样,@Cacheable用于划分可缓存的方法 - 即,将结果存储到缓存中的方法,以便在后续调用(具有相同的 arguments)时,返回缓存中的 value 而不必实际返回执行方法。在最简单的形式中,annotation 声明需要与带注释的方法关联的缓存的 name:

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

在上面的代码段中,方法findBook与名为books的缓存相关联。调用该方法的每个 time 时,都会检查缓存以查看调用是否已经执行且不必重复。虽然在大多数情况下,只声明了一个缓存,但 annotation 允许指定多个名称,以便使用多个缓存。在这种情况下,将在执行方法之前检查每个缓存 - 如果至少有一个缓存被命中,则将返回关联的 value:

即使没有实际执行缓存的方法,所有其他不包含 value 的缓存也将更新。

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

默认 Key Generation

由于高速缓存本质上是 key-value stores,因此需要将每个高速缓存方法的调用转换为适合高速缓存访问的 key。开箱即用,缓存抽象使用基于以下算法的简单KeyGenerator

  • 如果没有给出参数,return SimpleKey.EMPTY

  • 如果只给出一个参数,则 return 该实例。

  • 如果给出了更多的参数,则 return SimpleKey包含所有参数。

这种方法适用于大多数 use-cases;作为 long,参数具有自然键并实现有效的hashCode()equals()方法。如果情况并非如此,则需要更改策略。

要提供不同的默认 key generator,需要实现org.springframework.cache.interceptor.KeyGenerator接口。

随着 Spring 4.0 的发布,默认的 key 生成策略发生了变化。早期版本的 Spring 使用了 key 生成策略,对于多个 key 参数,只考虑hashCode()参数而不是equals();这可能会导致意外的 key 碰撞(请参阅背景的SPR-10237)。新的'SimpleKeyGenerator'使用复合 key 来表示这种情况。

如果要继续使用以前的 key 策略,可以配置已弃用的org.springframework.cache.interceptor.DefaultKeyGenerator class 或创建自定义 hash-based'KeyGenerator'implementation。

自定义 Key Generation 声明

由于缓存是通用的,因此目标方法很可能具有各种签名,这些签名无法简单地映射到缓存结构之上。当目标方法具有多个 arguments 时,这往往变得明显,其中只有一些适合于缓存(而 rest 仅由方法逻辑使用)。例如:

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

乍一看,虽然两个boolean arguments 影响了书的发现方式,但它们对缓存没有用处。如果两个中只有一个重要而另一个不重要,那么还有什么呢?

对于这种情况,@Cacheable annotation 允许用户通过其key属性指定 key 的生成方式。开发人员可以使用规划环境地政司来选择感兴趣的 arguments(或其嵌套的 properties),执行操作甚至调用任意方法,而无需编写任何 code 或实现任何接口。这是默认 generator的推荐方法,因为随着 code 基数的增长,签名方法在签名方面往往会有很大差异;虽然默认策略可能适用于某些方法,但它很少适用于所有方法。

下面是各种 SpEL 声明的一些示例 - 如果您不熟悉它,请自己帮忙并阅读第 10 章,Spring 表达式语言(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)

上面的片段显示了选择某个参数,其中一个 properties 甚至是一个任意(静态)方法是多么容易。

如果负责生成 key 的算法太具体或者需要共享,则可以在操作上定义自定义keyGenerator。为此,请指定要使用的KeyGenerator bean implementation 的 name:

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

keykeyGenerator参数是互斥的,指定两者的操作将导致 exception。

默认缓存分辨率

开箱即用,缓存抽象使用一个简单的CacheResolver,它使用配置的CacheManager检索在 level 操作中定义的 cache(s。

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

自定义缓存分辨率

默认缓存分辨率非常适合使用单个CacheManager且没有复杂缓存分辨率要求的 applications。

对于使用多个缓存管理器的 applications,可以将cacheManager设置为使用每个操作:

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

也可以以与key 代类似的方式完全替换CacheResolver。为每个缓存操作请求解析,使 implementation 有机会基于 runtime arguments 实际解析 cache(s):

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

从 Spring 4.1 开始,cache annotations 的value属性不再是必需的,因为无论 annotation 的内容如何,CacheResolver都可以提供此特定信息。

keykeyGenerator类似,cacheManagercacheResolver参数是互斥的,并且指定两者的操作将导致 exception,因为CacheResolver implementation 将忽略自定义CacheManager。这可能不是你所期望的。

同步缓存

在 multi-threaded 环境中,可能会为同一参数同时调用某些操作(通常在启动时)。默认情况下,缓存抽象不会锁定任何内容,并且可能会多次计算相同的 value,从而无法实现缓存。

对于这些特定情况,sync属性可用于指示底层缓存提供程序在计算 value 时锁定缓存条目。因此,只有一个线程忙于计算 value,而其他线程将被阻塞,直到缓存中的条目更新为止。

@Cacheable(cacheNames="foos", sync=true)
public Foo executeExpensiveOperation(String id) {...}

这是一个可选的 feature,您最喜欢的缓存 library 可能不支持它。核心 framework 提供的所有CacheManager _implement 都支持它。有关更多详细信息,请查看缓存提供程序的文档。

条件缓存

有时,方法可能不适合缓存所有 time(对于 example,它可能取决于给定的 arguments)。 cache annotations 通过condition参数支持此类功能,该参数采用SpEL表达式,该表达式被计算为truefalse。如果true,则缓存该方法 - 如果不是,则其行为就好像该方法未缓存,无论缓存中的值是什么,或者使用了哪些 arguments,都会每隔 time 执行一次。快速 example - 仅当参数name的长度小于 32 时,才会缓存以下方法:

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

另外,condition参数,unless参数可用于否决向缓存添加 value。与condition不同,unless表达式在调用方法后进行计算。扩展前一个 example - 也许我们只想缓存平装书:

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

缓存抽象支持java.util.Optional,仅在其存在时将其内容用作缓存 value。 #result始终引用业务实体,从不在受支持的 wrapper 上,因此可以按如下方式重写上一个 example:

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

请注意,result仍然是指Book而不是Optional。因为它可能是null,我们应该使用安全导航 operator。

可用缓存 SpEL evaluation context

每个SpEL表达式再次评估专用的context。除了参数中的 build 之外,framework 还提供专用的缓存相关元数据,例如参数名称。下一个 table 列出 context 可用的项目,以便可以将它们用于 key 和条件计算:

表格 1_.缓存 SpEL 可用元数据

名称地点描述
方法名root object要调用的方法的 name#root.methodName
方法root object正在调用的方法#root.method.name
目标root object正在调用目标 object#root.target
targetClassroot object正在调用的目标的 class#root.targetClass
ARGSroot objectarguments(as array)用于调用目标#root.args[0]
高速缓存root object执行当前方法的高速缓存的集合#root.caches[0].name
参数 nameevaluation context任何方法 arguments 的 Name。如果由于某种原因名称不可用(e.g. 无调试信息),则参数名称也可在#a<#arg>下获得,其中#arg 代表参数索引(从 0 开始)。#iban#a0(也可以使用#p0#p<#arg>表示法作为别名)。
结果evaluation context方法调用的结果(要缓存的 value)。仅在unless表达式,cache put表达式(用于计算key)或cache evict表达式(当beforeInvocationfalse时)中可用。对于支持的包装器,例如Optional#result指的是实际的 object,而不是 wrapper。#result

36.3.2 @CachePut annotation

对于需要更新缓存而不干扰方法执行的情况,可以使用@CachePut annotation。也就是说,将始终执行该方法并将其结果放入缓存中(根据@CachePut选项)。它支持与@Cacheable相同的选项,应该用于缓存填充而不是方法流优化:

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)

请注意,通常强烈建议不要在同一方法上使用@CachePut@Cacheable 注释,因为它们具有不同的行为。虽然后者导致通过使用缓存跳过方法执行,但前者强制执行 order 以执行缓存更新。这会导致意外的行为,并且特定 corner-cases 的 exception(例如 annotations 具有将它们彼此排除的条件),应该避免这种声明。另请注意,此类条件不应依赖于结果 object(i.e.#result变量),因为这些条件已经过验证,以确认排除。

36.3.3 @CacheEvict annotation

缓存抽象不仅允许缓存 store 的填充,还允许驱逐。此 process 对于从缓存中删除过时或未使用的数据非常有用。与@Cacheable相反,annotation @CacheEvict划分了执行缓存逐出的方法,即用作从缓存中删除数据的触发器的方法。就像它的兄弟一样,@CacheEvict需要指定一个(或多个)受操作影响的缓存,允许自定义缓存和 key 解析或条件被指定,但另外,features 一个额外的参数allEntries,表示 cache-wide 驱逐是否需要要执行而不只是一个条目(基于 key):

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

当需要清除整个缓存区域时,此选项会派上用场 - 而不是逐出每个条目(由于效率低,需要 long time),所有条目都会在一个操作中被删除,如上所示。请注意,framework 将忽略此方案中指定的任何 key,因为它不适用(整个缓存不仅驱逐一个条目)。

还可以指示驱逐是在(默认)之后还是在方法通过beforeInvocation属性执行之前发生的。前者提供与 annotations 的 rest 相同的语义 - 一旦方法成功完成,就会执行缓存上的操作(在本例中为逐出)。如果方法未执行(因为它可能被缓存)或抛出 exception,则不会发生逐出。后者(beforeInvocation=true)导致驱逐始终在调用方法之前发生 - 这在驱逐不需要与方法结果相关联的情况下是有用的。

重要的是要注意 void 方法可以与@CacheEvict一起使用 - 因为方法充当触发器,return 值被忽略(因为它们不与缓存交互) - 这不是@Cacheable的情况,adds/updates 数据进入缓存,因此需要一个结果。

36.3.4 @Caching annotation

对于 example,有些情况需要指定相同类型的多个注释,例如@CacheEvict@CachePut,因为不同缓存之间的条件或 key 表达式不同。 @Caching允许在同一方法上使用多个嵌套的@Cacheable@CachePut@CacheEvict

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

36.3.5 @CacheConfig annotation

到目前为止,我们已经看到缓存操作提供了许多自定义选项,这些可以在操作的基础上设置。但是,如果某些自定义选项适用于 class 的所有操作,则可能需要配置一些自定义选项。例如,指定要用于 class 的每个高速缓存操作的高速缓存的 name 可以由单个 class-level 定义替换。这是@CacheConfig发挥作用的地方。

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

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

@CacheConfig是一个 class-level annotation,允许共享缓存名称,自定义KeyGenerator,自定义CacheManager,最后是自定义CacheResolver。将此 annotation 放在 class 上不会打开任何缓存操作。

operation-level 自定义将始终覆盖@CacheConfig上的自定义集。因此,这为每个缓存操作提供了三个级别的自定义:

  • 全局配置,可用于CacheManagerKeyGenerator

  • 在 class level,使用@CacheConfig

  • 在 level 操作

36.3.6 启用缓存注释

重要的是要注意,尽管声明缓存注释不会自动触发它们的操作 - 就像 Spring 中的许多内容一样,feature 必须以声明方式启用(这意味着如果您怀疑缓存是责任,您可以通过删除来禁用它只有一个 configuration line 而不是 code 中的所有 annotations。

要启用缓存注释,请将注释@EnableCaching添加到@Configuration classes 中的一个:

@Configuration
@EnableCaching
public class AppConfig {
}

或者,对于 XML configuration,使用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 http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

        <cache:annotation-driven/>
</beans>

cache:annotation-driven元素和@EnableCaching annotation 都允许指定各种选项,这些选项会影响通过 AOP 将缓存行为添加到 application 的方式。 configuration 有意与@Transactional类似:

处理缓存注释的默认建议模式是“代理”,它允许仅通过代理拦截 calls;同一 class 中的 local calls 不能以这种方式截获。对于更高级的拦截模式,请考虑结合 compile-time 或 load-time 编织切换到“aspectj”模式。

使用 Java 配置的高级自定义需要实现CachingConfigurer:请参阅javadoc 了解更多细节

表格 1_.缓存注释设置

XML 属性Annotation 属性默认描述
cache-managerN/A(见CachingConfigurer javadocs)CacheManager要使用的缓存 manager 的名称。默认CacheResolver将在后台使用此缓存 manager 初始化(如果未设置,则为cacheManager)。有关缓存分辨率的更多 fine-grained 管理,请考虑设置“cache-resolver”属性。
cache-resolverN/A(见CachingConfigurer javadocs)A SimpleCacheResolver使用配置的cacheManager要用于解析后备高速缓存的 CacheResolver 的 bean name。此属性不是必需的,只需要指定为“cache-manager”属性的替代。
key-generatorN/A(见CachingConfigurer javadocs)SimpleKeyGenerator要使用的自定义 key generator 的名称。
error-handlerN/A(见CachingConfigurer javadocs)SimpleCacheErrorHandler要使用的自定义缓存错误处理程序的名称。默认情况下,缓存相关操作期间的任何 exception 抛出都会返回到 client。
modemode代理默认模式“proxy”使用 Spring 的 AOP framework 处理带注释的 beans 代理(遵循代理语义,如上所述,仅应用于通过代理进入的方法 calls)。替代模式“aspectj”用 Spring 的 AspectJ 缓存 aspect 编织受影响的 classes,修改目标 class byte code 以应用于任何类型的方法调用。 AspectJ 编织需要在 classpath 中启用 spring-aspects.jar 以及启用 load-time 编织(或 compile-time 编织)。 (有关如何设置 load-time weaving.)的详细信息,请参阅名为“Spring configuration”的部分
proxy-target-classproxyTargetClass仅适用于代理模式。控制为使用@Cacheable@CacheEvict 注释注释的 class 创建的缓存代理类型。如果proxy-target-class属性设置为true,则创建 class-based 代理。如果proxy-target-classfalse或者省略了该属性,则会创建标准 JDK interface-based 代理。 (有关不同代理人的详细检查,请参阅第 11.6 节,“代理机制” types.)
orderorderOrdered.LOWEST_PRECEDENCE定义应用于使用@Cacheable@CacheEvict注释的 beans 的缓存建议的 order。 (有关与 AOP 建议的排序相关的规则的更多信息,请参阅名为“建议订购”的部分 .)没有指定的 ordering 意味着 AOP 子系统确定建议的 order。

<cache:annotation-driven/>只在 beans 中查找@Cacheable/@CachePut/@CacheEvict/@Caching在它定义的相同 application context 中。这意味着,如果你将<cache:annotation-driven/>放在WebApplicationContextDispatcherServlet,它只检查控制器中的 beans,而不检查你的服务。有关更多信息,请参见第 22.2 节,“DispatcherServlet”


方法可见性和缓存注释

使用代理时,应将 cache annotations 仅应用于具有公共可见性的方法。如果使用这些注释注释 protected,private 或 package-visible 方法,则不会引发错误,但带注释的方法不会显示已配置的缓存设置。如果需要注释 non-public 方法,请考虑使用 AspectJ(见下文),因为它会更改字节码本身。


Spring 建议您只使用@Cache* annotation 注释具体的 classes(以及具体 classes 的方法),而不是注释接口。您当然可以将@Cache* annotation 放在接口(或接口方法)上,但这只能在您使用 interface-based 代理时按预期工作。 Java annotations 不是从接口继承的事实意味着如果您使用 class-based 代理(proxy-target-class="true")或 weaving-based aspect(mode="aspectj"),则代理和编织基础结构无法识别缓存设置,并且 object 将不会被包装在缓存代理中,这将是非常糟糕的。

在代理模式(默认设置)下,只拦截通过代理进入的外部方法 calls。这意味着 self-invocation 实际上是目标 object 中调用 target object 的另一个方法的方法,即使被调用的方法用@Cacheable标记,也不会在运行时导致实际的缓存 - 在这种情况下考虑使用 aspectj 模式。此外,必须完全初始化代理以提供预期的行为,因此您不应该在初始化 code,i.e 中依赖此 feature。 @PostConstruct

36.3.7 使用自定义注释


自定义注释和 AspectJ

此 feature 仅在 proxy-based 方法下工作 out-of-the-box,但可以使用 AspectJ 进行一些额外的工作。

spring-aspects模块仅为标准 annotations 定义 aspect。如果您已经定义了自己的注释,则还需要为这些注释定义 aspect。检查AnnotationCacheAspect是否为 example。


缓存抽象允许您使用自己的注释来标识触发缓存填充或驱逐的方法。这作为一种模板机制非常方便,因为它消除了复制缓存注释声明的需要(如果指定了 key 或条件,则特别有用),或者在 code 基础中不允许外部导入(org.springframework)。与铅板 annotations 的 rest 类似,@Cacheable@CachePut@CacheEvict@CacheConfig可以用作meta-annotations,这是可以注释其他注释的注释。也就是说,让我们用自己的自定义 annotation 替换 common @Cacheable声明:

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

上面,我们定义了自己的SlowService annotation,它本身用@Cacheable注释 - 现在我们可以替换下面的 code:

@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,容器也会在运行时自动获取其声明并理解其含义。请注意,如上所述以上,需要启用 annotation-driven 行为。

36.4 JCache(JSR-107)注释

自 Spring Framework 4.1 以来,缓存抽象完全支持 JCache 标准 annotations:这些是@CacheResult@CachePut@CacheRemove@CacheRemoveAll以及@CacheDefaults@CacheKey@CacheValue伴侣。这些注释可以直接使用,而无需将缓存 store 迁移到 JSR-107:内部 implementation 使用 Spring 的缓存抽象,并提供符合规范的默认CacheResolverKeyGenerator_mplempleations。换句话说,如果您已经在使用 Spring 的缓存抽象,则可以切换到这些标准注释而无需更改缓存存储(或 configuration)。

36.4.1 功能摘要

对于那些熟悉 Spring 的缓存注释的人,下面的 table 描述了 Spring annotations 和 JSR-107 对应物之间的主要区别:

表格 1_.Spring vs. JSR-107 缓存注释

弹簧JSR-107备注
@Cacheable@CacheResult非常相似。 @CacheResult可以缓存特定的 exceptions 并强制执行该方法,而不管缓存的内容如何。
@CachePut@CachePut当 Spring 使用方法调用的结果更新缓存时,JCache 需要将其作为使用@CacheValue注释的参数传递。由于这种差异,JCache 允许在实际方法调用之前或之后更新缓存。
@CacheEvict@CacheRemove非常相似。如果方法调用导致 exception,则@CacheRemove支持条件逐出。
@CacheEvict(allEntries=true)@CacheRemoveAll@CacheRemove
@CacheConfig@CacheDefaults允许以类似的方式配置相同的概念。

JCache 的概念javax.cache.annotation.CacheResolver与 Spring 的CacheResolver接口相同,只是 JCache 只支持单个缓存。默认情况下,一个简单的 implementation 根据 annotation 上声明的 name 检索要使用的缓存。应该注意的是,如果在 annotation 上没有指定 cache name,则自动生成默认值,检查@CacheResult#cacheName()的 javadoc 以获取更多信息。

CacheResolverFactory实例由CacheResolverFactory检索。可以自定义每个缓存操作的工厂:

@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class)
public Book findBook(ISBN isbn)

对于所有引用的 classes,Spring 尝试找到具有给定类型的 bean。如果存在多个 match,则会创建一个新实例,并且可以使用常规 bean 生命周期回调,例如依赖注入。

密钥由javax.cache.annotation.CacheKeyGenerator生成,其作用与 Spring 的KeyGenerator相同。默认情况下,除非至少有一个参数使用@CacheKey注释,否则将考虑所有方法 arguments。这类似于 Spring 的自定义 key 生成声明。例如,这些是相同的操作,一个使用 Spring 的抽象,另一个使用 JCache:

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

@CacheResult(cacheName="books")
public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed)

也可以在操作中指定要使用的CacheKeyResolver,方式与CacheResolverFactory类似。

JCache 可以管理由带注释的方法抛出的 exceptions:这可以阻止更新缓存,但它也可以将 exception 缓存为失败的指示器,而不是再次调用该方法。假设如果 ISBN 的结构无效,则抛出InvalidIsbnNotFoundException。这是一个永久性的失败,没有任何书籍可以用这样的参数检索。下面缓存 exception,以便进一步 calls 具有相同的无效 ISBN,直接抛出缓存的 exception,而不是再次调用该方法。

@CacheResult(cacheName="books", exceptionCacheName="failures"
            cachedExceptions = InvalidIsbnNotFoundException.class)
public Book findBook(ISBN isbn)

36.4.2 启用 JSR-107 支持

在 Spring 的声明性 annotation 支持下,没有必要做任何特定的事情来启用 JSR-107 支持。如果 class 路径中存在 JSR-107 API 和spring-context-support模块,则@EnableCachingcache:annotation-driven元素将自动启用 JCache 支持。

根据您的使用情况,选择基本上是您的。您甚至可以使用 JSR-107 API 和其他使用 Spring 自己的注释来混合和 match 服务。但请注意,如果这些服务影响相同的缓存,则应使用一致且相同的 key generation implementation。

36.5 声明式 XML-based 缓存

如果 annotations 不是一个选项(无法访问源或没有外部 code),则可以使用 XML 进行声明性缓存。因此,不是注释缓存方法,而是在外部指定目标方法和缓存指令(类似于声明性 transaction management 忠告)。上一个 example 可以翻译成:

<!-- the service we want to make cacheable -->
<bean id="bookService" class="x.y.service.DefaultBookService"/>

<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
    <cache:caching cache="books">
        <cache:cacheable method="findBook" key="#isbn"/>
        <cache:cache-evict method="loadBooks" all-entries="true"/>
    </cache:caching>
</cache:advice>

<!-- apply the cacheable behavior to all BookService interfaces -->
<aop:config>
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.BookService.*(..))"/>
</aop:config>

<!-- cache manager definition omitted -->

在上面的 configuration 中,bookService可以缓存。要应用的缓存语义封装在cache:advice定义中,该定义指示方法findBooks用于将数据放入高速缓存,而方法loadBooks用于驱逐数据。这两个定义都适用于books缓存。

aop:config定义通过使用 AspectJ 切入点表达式将缓存建议应用于程序中的适当点(更多信息在第 11 章,使用 Spring 进行面向对象编程中可用)。在上面的 example 中,将考虑BookService中的所有方法,并将缓存建议应用于它们。

声明性 XML 缓存支持所有 annotation-based model,因此在两者之间移动应该相当容易 - 更多可以在同一个 application 中使用。基于 XML 的方法不会触及目标 code,但它本身就更加冗长;当使用重载方法处理 classes 时,识别正确的方法确实需要额外的努力,因为method参数不是一个好的鉴别器 - 在这些情况下,AspectJ 切入点可以用来挑选目标方法并应用适当的缓存功能。但是,通过 XML,更容易应用 package/group/interface-wide 缓存(同样由于 AspectJ 切入点)并创建 template-like 定义(正如我们在 example 中通过cache:definitions cache属性定义目标缓存所做的那样)。

36.6 配置缓存存储

开箱即用,缓存抽象提供了几个存储集成。要使用它们,需要简单地声明一个适当的CacheManager - 一个控制和管理Cache的实体,并可用于检索这些存储。

36.6.1 JDK ConcurrentMap-based 缓存

JDK-based Cache implementation 位于org.springframework.cache.concurrent包下。它允许使用ConcurrentHashMap作为后台Cache store。

<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
        </set>
    </property>
</bean>

上面的代码片段使用SimpleCacheManager为名为 default 和 books 的两个嵌套ConcurrentMapCache实例创建CacheManager。请注意,名称是直接为每个缓存配置的。

由于缓存是由 application 创建的,因此它被绑定到它的生命周期,使其适用于基本用例,测试或简单的应用程序。缓存可以很好地扩展并且非常快,但它不提供任何管理或持久性功能,也不提供_etractions。

36.6.2 Ehcache-based 缓存

Ehcache 3.x 完全符合 JSR-107 标准,不需要专门的支持。

Ehcache 2.x implementation 位于org.springframework.cache.ehcache包下。再次,要使用它,只需要声明适当的CacheManager

<bean id="cacheManager"
        class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>

<!-- EhCache library setup -->
<bean id="ehcache"
        class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>

这个设置引导 Spring IoC 中的 ehcache library(通过ehcache bean),然后连接到专用的CacheManager __mplementation。注意整个 ehcache-specific configuration 是从ehcache.xml读取的。

36.6.3 Caffeine Cache

Caffeine 是 Guava 缓存的 Java 8 rewrite,它的 implementation 位于org.springframework.cache.caffeine包下,提供对 Caffeine 的几个 feature 的访问。

配置按需创建缓存的CacheManager非常简单:

<bean id="cacheManager"
        class="org.springframework.cache.caffeine.CaffeineCacheManager"/>

还可以提供明确使用的高速缓存。在这种情况下,只有那些将由 manager 提供:

<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
    <property name="caches">
        <set>
            <value>default</value>
            <value>books</value>
        </set>
    </property>
</bean>

Caffeine CacheManager也支持海关CaffeineCacheLoader。有关这些内容的详细信息,请参阅Caffeine 文档

36.6.4 Guava 缓存

Guava implementation 位于org.springframework.cache.guava包下,提供对 Guava 的几个 feature 的访问。

配置按需创建缓存的CacheManager非常简单:

<bean id="cacheManager"
      class="org.springframework.cache.guava.GuavaCacheManager"/>

还可以提供明确使用的高速缓存。在这种情况下,只有那些将由 manager 提供:

<bean id="cacheManager" class="org.springframework.cache.guava.GuavaCacheManager">
    <property name="caches">
        <set>
            <value>default</value>
            <value>books</value>
        </set>
    </property>
</bean>

Guava CacheManager也支持海关CacheBuilderCacheLoader。有关这些内容的详细信息,请参阅Guava 文档

36.6.5 GemFire-based 缓存

GemFire 是一个 memory-oriented/disk-backed,可弹性扩展,持续可用,active(具有 built-in pattern-based 订阅通知),全局复制数据库并提供 fully-featured 边缘缓存。有关如何将 GemFire 用作 CacheManager(以及更多)的更多信息,请参阅Spring Data GemFire reference 文档

36.6.6 JSR-107 缓存

Spring 的缓存抽象也可以使用符合 JSR-107 的缓存。 JCache implementation 位于org.springframework.cache.jcache包下。

再次,要使用它,只需要声明适当的CacheManager

<bean id="cacheManager"
        class="org.springframework.cache.jcache.JCacheCacheManager"
        p:cache-manager-ref="jCacheManager"/>

<!-- JSR-107 cache manager setup  -->
<bean id="jCacheManager" .../>

36.6.7 处理没有支持 store 的缓存

有时在切换环境或进行测试时,可能会有缓存声明而没有配置实际的后备缓存。由于这是一个无效的 configuration,在运行时会抛出 exception,因为缓存基础结构无法找到合适的 store。在这样的情况下,而不是删除缓存声明(这可能证明是乏味的),可以连接一个不执行缓存的简单虚拟缓存 - 也就是说,强制每隔 time 执行缓存的方法:

<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
    <property name="cacheManagers">
        <list>
            <ref bean="jdkCache"/>
            <ref bean="gemfireCache"/>
        </list>
    </property>
    <property name="fallbackToNoOpCache" value="true"/>
</bean>

上面的CompositeCacheManager链多个CacheManager s,另外,通过fallbackToNoOpCache flag,为所有未由配置的缓存 managers 处理的定义添加一个 no op 缓存。也就是说,在jdkCachegemfireCache(上面配置)中找不到的每个缓存定义都将由 no op 缓存处理,该缓存不会存储导致每隔 time 执行目标方法的任何信息。

36.7 Plugging-in 个不同的 back-end 缓存

很明显,有很多缓存产品可以用作支持 store。要插入它们,需要提供CacheManagerCache implementation,因为遗憾的是我们没有可用的标准。这可能听起来比实际上更难,因为在实践中,class 往往是简单的适配器s,它将ehcache classes 可以显示的存储 API 上的缓存抽象 framework 映射。大多数CacheManager classes 可以使用org.springframework.cache.support包中的 classes,例如AbstractCacheManager来处理 boiler-plate code,只留下实际的映射完成。我们希望在 time 中,提供 integration 与 Spring 的 libraries 可以填补这个小的 configuration 空白。

36.8 如何设置 TTL/TTI/Eviction policy/XXX feature?

直接通过缓存提供程序。缓存抽象是......好吧,抽象不是缓存实现。您正在使用的解决方案可能支持各种数据 policies 和其他解决方案所不具备的不同拓扑(例如 JDK ConcurrentHashMap) - 暴露在缓存抽象中仅仅因为没有后备支持而无用。在配置或通过其本机 API 时,应通过后备缓存直接控制此类功能。

Updated at: 2 months ago
35.6. 更多资源Table of contentVIII. 附录
Comment
You are not logged in.

There are no comments.