SpringCache

Posted 天宇轩-王

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCache相关的知识,希望对你有一定的参考价值。

官网介绍中文翻译如下: 

8.缓存抽象

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

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

 8.1. 了解缓存抽象

缓存与缓冲区

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

另一方面,高速缓存根据定义是隐藏的,并且任何一方都不知道发生了高速缓存。它还改善了 performance,但是通过快速方式多次读取相同的数据来实现这一点。

您可以找到缓冲区和缓存这里之间差异的进一步说明。

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

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

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

与 Spring Framework 中的其他服务一样,缓存服务是抽象(不是缓存实现),并且需要使用实际存储来存储缓存数据 - 也就是说,抽象使您无需编写缓存逻辑,但是不提供实际数据 store。这种抽象由org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口实现。

Spring 提供了一些实现的抽象:基于 JDK java.util.concurrent.ConcurrentMap的缓存,Ehcache 2.x,Gemfire 缓存,咖啡因和 JSR-107 兼容缓存(例如 Ehcache 3.x)。有关插入其他缓存存储和提供程序的更多信息,请参阅Plugging-in 不同的 Back-end 缓存。

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

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

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

要使用缓存抽象,您需要注意两个方面:

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

  • 高速缓存 configuration:存储数据并从中读取数据的后备高速缓存。

 8.2. 声明式 Annotation-based 缓存

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

  • @Cacheable:触发缓存填充。

  • @CacheEvict:触发缓存逐出。

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

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

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

 8.2.1. @Cacheable Annotation

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

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

 

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

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

以下 example 在findBook方法上使用@Cacheable

@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 仅由方法逻辑使用)。考虑以下 example:

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

 

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

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

以下示例是各种 SpEL 声明(如果您不熟悉 SpEL,请自己帮忙并阅读Spring 表达语言):

@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,如下面的 example 所示:

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

 

keykeyGenerator参数是互斥的,并且指定两者的操作都会导致 exception。

默认缓存分辨率

缓存抽象使用一个简单的CacheResolver,它使用配置的CacheManager检索操作 level 中定义的缓存。

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

自定义缓存分辨率

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

对于适用于多个缓存 managers 的 applications,您可以设置cacheManager用于每个操作,如下面的 example 所示:

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

 

  
1 指定anotherCacheManager

您也可以以类似于替换key 代的方式完全替换CacheResolver。为每个缓存操作请求解析,让 implementation 实际上解析基于 runtime arguments 使用的缓存。以下 example 显示了如何指定CacheResolver

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

 

  
1 指定CacheResolver

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

keykeyGenerator类似,cacheManagercacheResolver参数是互斥的,指定两者的操作都会导致 exception。作为自定义CacheManagerCacheResolver implementation 忽略。这可能不是你所期望的。

同步缓存

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

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

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

 

  
1 使用sync属性。

这是一个可选的 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") (1)
public Book findBook(String name)

 

  
1 @Cacheable上设置条件。

除了condition参数之外,您还可以使用unless参数来否决向缓存添加 value。与condition不同,unless表达式在调用方法后进行计算。要扩展上一个 example,也许我们只想缓存平装书,如下例所示:

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

 

  
1 使用unless属性来阻止精装本。

高速缓存抽象支持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进行求值。除 built-in 参数外,framework 还提供专用的 caching-related 元数据,例如参数名称。以下 table 描述了 context 可用的项目,以便您可以将它们用于 key 和条件计算:

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

 8.2.2. @CachePut Annotation

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

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

 

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

 8.2.3. @CacheEvict annotation

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

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

 

  
1 使用allEntries属性驱逐缓存中的所有条目。

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

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

请注意void方法可以与@CacheEvict一起使用 - 因为方法充当触发器,return 值被忽略(因为它们不与缓存交互)。 @Cacheable不是这种情况,它将数据添加或更新到缓存中,因此需要一个结果。

 8.2.4. @Caching Annotation

有时,需要指定多个相同类型的注释(例如@CacheEvict@CachePut) - 例如,因为条件或 key 表达式在不同的缓存之间是不同的。 @Caching允许在同一方法上使用多个嵌套的@Cacheable@CachePut@CacheEvict 注释。以下 example 使用两个@CacheEvict 注释:

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

 

 8.2.5. @CacheConfig annotation

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

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

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

 

  
1 使用@CacheConfig设置缓存的 name。

@CacheConfig是注释,允许共享缓存名称,自定义KeyGenerator,自定义CacheManager和自定义CacheResolver。将此 annotation 放在 class 上不会打开任何缓存操作。

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

  • 全局配置,可用于CacheManagerKeyGenerator

  • 在 class level 中,使用@CacheConfig

  • 在 level 操作。

 8.2.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类似。

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

有关实现CachingConfigurer所需的高级自定义(使用 Java configuration)的更多详细信息,请参阅javadoc。

XML 属性Annotation 属性默认描述
cache-manager N/A(参见CachingConfigurerjavadoc) cacheManager 要使用的缓存 manager 的 name。使用此缓存 manager 在后台初始化默认CacheResolver(如果未设置,则为cacheManager)。有关缓存分辨率的更多 fine-grained 管理,请考虑设置“cache-resolver”属性。
cache-resolver N/A(参见CachingConfigurerjavadoc) SimpleCacheResolver使用配置的cacheManager 要用于解析后备高速缓存的 CacheResolver 的 bean name。此属性不是必需的,只需要指定为‘cache-manager‘属性的替代。
key-generator N/A(参见CachingConfigurerjavadoc) SimpleKeyGenerator 要使用的自定义 key generator 的名称。
error-handler N/A(参见CachingConfigurerjavadoc) SimpleCacheErrorHandler 要使用的自定义缓存错误处理程序的 name。默认情况下,在缓存相关操作期间抛出的任何 exception 都会返回到 client。
mode mode proxy 默认模式(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-class proxyTargetClass false 仅适用于代理模式。控制为使用@Cacheable@CacheEvict 注释注释的 class 创建的缓存代理类型。如果proxy-target-class属性设置为true,则会创建 class-based 个代理。如果proxy-target-classfalse或者省略了该属性,则会创建标准 JDK interface-based 代理。 (有关不同代理人的详细检查,请参阅代理机制 types.)
order order Ordered.LOWEST_PRECEDENCE 定义应用于使用@Cacheable@CacheEvict注释的 beans 的缓存建议的 order。 (有关与排序 AOP 建议相关的规则的更多信息,请参阅建议订购 .)未指定 ordering 意味着 AOP 子系统确定建议的 order。

<cache:annotation-driven/>仅在定义它的同一 application context 中的 beans 上查找@Cacheable/@CachePut/@CacheEvict/@Caching。这意味着,如果您将<cache:annotation-driven/>放在WebApplicationContextDispatcherServlet,它只会在您的控制器中检查 beans,而不是您的服务。有关更多信息,请参见MVC 部分。

方法可见性和缓存注释

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

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 中的一个方法 calls 目标 object 的另一个方法)在运行时不会导致实际的缓存,即使被调用的方法用@Cacheable标记。在这种情况下,请考虑使用aspectj模式。此外,必须完全初始化代理以提供预期的行为,因此您不应该在初始化 code(即@PostConstruct)中依赖此 feature。

 8.2.7. 使用自定义注释

自定义 annotation 和 AspectJ

此 feature 仅适用于 proxy-based 方法,但可以通过使用 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 {
}

 

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

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

 

以下 example 显示了 custom annotation,我们可以使用它来替换前面的 code:

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

 

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

 8.3. JCache(JSR-107)Annotations

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

 8.3.1. Feature 摘要

对于那些熟悉 Spring 的缓存注释的人,下面的 table 描述了 Spring annotations 和 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) (1)
public Book findBook(ISBN isbn)

 

  
1 为此操作自定义工厂。

对于所有引用的 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)

 

 8.3.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。

 8.4. 声明式 XML-based 缓存

如果 annotations 不是一个选项(可能是由于无法访问源或没有外部 code),您可以使用 XML 进行声明性缓存。因此,您可以在外部指定目标方法和缓存指令,而不是注释缓存方法(类似于声明式 transaction management 忠告)。上一节中的 example 可以转换为以下 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 切入点表达式将缓存建议应用于程序中的适当点(更多信息在使用 Spring 进行面向对象编程中可用)。在前面的 example 中,将考虑BookService中的所有方法,并将缓存建议应用于它们。

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

 8.5. 配置缓存存储

缓存抽象提供了几个存储 integration 选项。要使用它们,您需要声明一个适当的CacheManager(一个控制和管理Cache实例的实体,可用于检索这些实例以进行存储)。

 8.5.1. JDK ConcurrentMap-based 缓存

JDK-based Cache implementation 位于org.springframework.cache.concurrent包下。它允许您使用ConcurrentHashMap作为后台Cache store。以下 example 显示了如何配置两个缓存:

<!-- 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为名为defaultbooks的两个嵌套ConcurrentMapCache实例创建CacheManager。请注意,名称是直接为每个缓存配置的。

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

 8.5.2. Ehcache-based 缓存

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

Ehcache 2.x implementation 位于org.springframework.cache.ehcache包中。同样,要使用它,您需要声明适当的CacheManager。以下 example 显示了如何执行此操作:

<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 implementation。请注意,从ehcache.xml读取整个 ehcache-specific configuration。

 8.5.3. Caffeine Cache

Caffeine 是 Guava 缓存的 Java 8 rewrite,它的 implementation 位于org.springframework.cache.caffeine包中,可以访问 Caffeine 的几个 feature。

以下 example 配置CacheManager,根据需要创建缓存:

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

您还可以提供明确使用的缓存。在这种情况下,只有那些由 manager 提供。以下 example 显示了如何执行此操作:

<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 文档。

 8.5.4. GemFire-based 缓存

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

 8.5.5. JSR-107 缓存

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

同样,要使用它,您需要声明适当的CacheManager。以下 example 显示了如何执行此操作:

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

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

 

 8.5.6. 在没有支持 Store 的情况下处理缓存

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

<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实例,并通过fallbackToNoOpCache flag 为所有未由配置的缓存 managers 处理的定义添加 no-op 缓存。也就是说,jdkCachegemfireCache(在 example 中先前配置)中找不到的每个缓存定义都由 no-op 缓存处理,该缓存不存储任何信息,导致每隔 time 执行一次目标方法。

 8.6. Plugging-in 不同的 Back-end 缓存

显然,有很多缓存产品可以用作支持 store。要插入它们,您需要提供CacheManagerCache implementation,因为遗憾的是,我们无法使用可用的标准。这可能听起来比实际上更难,因为在实践中,class 往往是简单的适配器,它将缓存抽象 framework 映射到存储 API 之上,就像ehcache classes 那样。大多数CacheManager classes 可以使用org.springframework.cache.support包中的 classes(例如AbstractCacheManager,它负责 boiler-plate code,只留下实际的映射完成)。我们希望,在 time 中,提供与 Spring 的整合的 libraries 可以填补这个小的 configuration 差距。

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

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

以上是关于SpringCache的主要内容,如果未能解决你的问题,请参考以下文章

博学谷学习记录超强总结,用心分享 | SpringCache常用注解介绍+集成redis

SpringCache通用缓存学习

SpringCache通用缓存学习

SpringCache自定义过期时间及自动刷新

shiro中部分SpringCache失效问题

SpringCache #yyds干货盘点#