Spring缓存

126 阅读21分钟

Spring缓存

1、基于声明注解的缓存

  • @Cacheable: 触发缓存填充。
  • @CacheEvict: 触发缓存逐出。
  • @CachePut: 在不干扰方法执行的情况下更新缓存。
  • @Caching: 重新组合要应用于方法的多个缓存操作。
  • @CacheConfig: 在类级别共享一些常见的缓存相关设置。

1.1、@Cacheable 注解

顾名思义,您可以使用@Cacheable 来划分可缓存的方法⟩——即结果存储在缓存中的方法,以便在后续调用(使用相同的参数)时返回缓存中的值而不必实际调用该方法。在其最简单的形式中,注释声明需要与注释方法关联的缓存名称,如以下示例所示:

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

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

以下示例在具有多个缓存的 findBook 方法上使用 @Cacheable:

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

1.1.1、默认key生成

由于缓存本质上是键值存储,因此缓存方法的每次调用都需要转换为适合缓存访问的键。缓存抽象使用基于以下算法的简单 KeyGenerator:

  • 如果没有给出参数,则返回 SimpleKey.EMPTY。
  • 如果只给出一个参数,则返回该实例。
  • 如果给出了多个参数,则返回一个包含所有参数的 SimpleKey。

这种方法适用于大多数用例,只要参数具有自然键并实现有效的 hashCode() 和 equals() 方法即可。如果不是这种情况,则需要更改策略。

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

1.1.2、自定义key生成声明

由于缓存是通用的,因此目标方法很可能具有无法轻易映射到缓存结构顶部的各种签名。当目标方法有多个参数时,这往往会变得很明显,其中只有一些参数适合缓存(而其余的仅由方法逻辑使用)。考虑以下示例:

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

乍一看,虽然这两个Boolean参数会影响查找Book的方式,但它们对缓存没有用处。此外,如果两者中只有一个重要而另一个不重要怎么办?

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

以下示例使用了各种 SpEL 声明(如果您不熟悉 SpEL,请帮自己一个忙,阅读 Spring Expression Language):

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

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

如果负责生成key的算法过于具体或者需要共享,您可以在操作上定义一个自定义的 keyGenerator。为此,请指定要使用的 KeyGenerator bean 实现的名称,如以下示例所示:

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

1.1.3、默认缓存解析

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

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

1.1.4、自定义缓存解析

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

对于使用多个缓存管理器的应用程序,您可以将 cacheManager 设置为用于每个操作,如以下示例所示:

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

指定 anotherCacheManager

您也可以采用类似于替换key生成的方式完全替换 CacheResolver。为每个缓存操作请求解析,让实现根据运行时参数实际解析要使用的缓存。以下示例显示如何指定 CacheResolver:

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

指定 CacheResolver

1.1.5、同步缓存

在多线程环境中,某些操作可能会为同一参数同时调用(通常在启动时)。默认情况下,缓存抽象不会锁定任何东西,同一个值可能会被计算多次,这违背了缓存的目的。

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

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

使用sync属性。

1.1.6、条件缓存

有时,一个方法可能不适合一直缓存(例如,它可能取决于给定的参数)。缓存注解通过条件参数支持此类用例,条件参数采用被评估为 true 或 false 的 SpEL 表达式。如果为真,则缓存该方法。如果没有,它的行为就好像该方法没有被缓存(也就是说,无论缓存中有什么值或使用什么参数,每次都会调用该方法)。例如,仅当参数名称的长度小于 32 时才缓存以下方法:

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

设定条件@Cacheable.

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

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

使用 unless 属性来阻止hardback。

缓存抽象支持 java.util.Optional 返回类型。如果存在 Optional 值,它将存储在关联的缓存中。如果不存在 Optional 值,则 null 将存储在关联的缓存中。 #result 总是引用业务实体而不是受支持的包装器,因此前面的示例可以重写如下:

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

请注意,#result 仍然指 Book 而不是 Optional。由于它可能为空,我们使用 SpEL 的safe navigation operator.。

1.1.7、可用缓存 SpEL 评估上下文

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

NameLocationDescriptionExample
methodNameRoot object被调用的方法的名称#root.methodName
methodRoot object被调用的方法#root.method.name
targetRoot object被调用的目标对象#root.target
targetClassRoot object被调用的目标类#root.targetClass
argsRoot object用于调用目标的参数(作为数组)#root.args[0]
cachesRoot object运行当前方法的缓存集合#root.caches[0].name
Argument nameEvaluation context任何方法参数的名称。如果名称不可用(可能是因为没有调试信息),参数名称也可以在#a<#arg> 下使用,其中#arg 代表参数索引(从 0 开始)。#iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias).
resultEvaluation context方法调用的结果(要缓存的值)。仅适用于 unless 表达式、缓存放置表达式(计算键)或缓存逐出表达式(当 beforeInvocation 为 false 时)。对于支持的包装器(例如 Optional),#result 指的是实际对象,而不是包装器。#result

1.2、@CachePut 注解

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

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

1.3、@CacheEvict 注解

缓存抽象不仅允许填充缓存存储,还允许逐出。此过程对于从缓存中删除陈旧或未使用的数据很有用。与@Cacheable 相反,@CacheEvict 划分执行缓存逐出的方法(即,充当从缓存中删除数据的触发器的方法)。与其兄弟类似,@CacheEvict 需要指定一个或多个受操作影响的缓存,允许自定义缓存和key解析或指定条件,并具有一个额外参数(allEntries),指示是否在缓存范围内逐出需要执行而不仅仅是条目驱逐(基于key)。以下示例从books缓存中逐出所有条目:

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

使用 allEntries 属性从缓存中逐出所有条目。

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

您还可以使用 beforeInvocation 属性指示逐出是在调用方法之后(默认)还是在调用方法之前发生。前者提供与其余注释相同的语义:方法成功完成后,将运行对缓存的操作(在本例中为逐出)。如果该方法不运行(因为它可能被缓存)或抛出异常,则不会发生驱逐。后者 (beforeInvocation=true) 导致逐出总是在调用方法之前发生。这在驱逐不需要绑定到方法结果的情况下很有用。

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

1.4、@Caching 注解

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

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

1.5、@CacheConfig 注解

到目前为止,我们已经看到缓存操作提供了许多自定义选项,您可以为每个操作设置这些选项。但是,如果某些自定义选项适用于该类的所有操作,那么它们的配置可能会很乏味。例如,指定用于类的每个缓存操作的缓存名称可以由单个类级定义代替。这就是@CacheConfig 发挥作用的地方。以下示例使用@CacheConfig 设置缓存的名称:

@CacheConfig("books") 
public class BookRepositoryImpl implements BookRepository {
​
    @Cacheable
    public Book findBook(ISBN isbn) {...}
}

使用@CacheConfig 设置缓存的名称。

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

操作级别的定制总是覆盖在@CacheConfig 上设置的定制。因此,这为每个缓存操作提供了三个级别的自定义:

  • 全局配置,可用于CacheManager、KeyGenerator。
  • 在类级别,使用@CacheConfig。
  • 在operation层面。

1.6、启用缓存注解

重要的是要注意,即使声明缓存注释不会自动触发它们的操作——就像 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 的配置相似。

缓存注解设置

XML AttributeAnnotation AttributeDefaultDescription
cache-managerN/A (see the CachingConfigurer javadoc)cacheManager要使用的缓存管理器的名称。默认的 CacheResolver 在后台使用此缓存管理器(如果未设置,则为 cacheManager)进行初始化。为了更细粒度地管理缓存解析,请考虑设置“缓存解析器”属性。
cache-resolverN/A (see the CachingConfigurer javadoc)A SimpleCacheResolver using the configured cacheManager.用于解析后备缓存的 CacheResolver 的 bean 名称。此属性不是必需的,仅需指定为“缓存管理器”属性的替代项。
key-generatorN/A (see the CachingConfigurer javadoc)SimpleKeyGenerator要使用的自定义key生成器的名称。
error-handlerN/A (see the CachingConfigurer javadoc)SimpleCacheErrorHandler要使用的自定义缓存错误处理程序的名称。默认情况下,在缓存相关操作期间抛出的任何异常都会在客户端抛回。
modemodeproxy默认模式(代理)使用 Spring 的 AOP 框架处理要代理的注释 bean(遵循代理语义,如前所述,仅适用于通过代理传入的方法调用)。替代模式 (aspectj) 将受影响的类与 Spring 的 AspectJ 缓存方面编织在一起,修改目标类字节代码以应用于任何类型的方法调用。 AspectJ 编织需要类路径中的 spring-aspects.jar 以及启用的加载时编织(或编译时编织)。 (有关如何设置加载时编织的详细信息,请参阅 Spring 配置。)
proxy-target-classproxyTargetClassfalse仅适用于代理模式。控制为使用 @Cacheable 或 @CacheEvict 注释注释的类创建什么类型的缓存代理。如果 proxy-target-class 属性设置为 true,则会创建基于类的代理。如果 proxy-target-class 为 false 或省略该属性,则会创建标准的基于 JDK 接口的代理。 (有关不同代理类型的详细检查,请参阅代理机制。)
orderorderOrdered.LOWEST_PRECEDENCE定义应用于使用 @Cacheable 或 @CacheEvict 注释的 bean 的缓存建议的顺序。 (有关与排序 AOP 建议相关的规则的更多信息,请参阅建议排序。)没有指定的顺序意味着 AOP 子系统确定建议的顺序。

1.7、使用自定义注解

缓存抽象允许您使用自己的注释来识别触发缓存填充或逐出的方法。这作为一种模板机制非常方便,因为它消除了重复缓存注释声明的需要,如果指定了键或条件,或者如果您的代码库中不允许外部导入 (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 注释,容器也会在运行时自动获取它的声明并理解它的含义。请注意,如前所述,需要启用注释驱动的行为。

2、JCache (JSR-107) 注解

从 4.1 版开始,Spring 的缓存抽象完全支持 JCache 标准 (JSR-107) 注解:@CacheResult、@CachePut、@CacheRemove 和 @CacheRemoveAll 以及 @CacheDefaults、@CacheKey 和 @CacheValue 同伴。即使不将缓存存储迁移到 JSR-107,您也可以使用这些注释。内部实现使用 Spring 的缓存抽象,并提供符合规范的默认 CacheResolver 和 KeyGenerator 实现。换句话说,如果您已经在使用 Spring 的缓存抽象,则可以切换到这些标准注释,而无需更改缓存存储(或配置)。

2.1、功能总结

对于那些熟悉 Spring 的缓存注释的人来说,下表描述了 Spring 注释与其 JSR-107 对应物之间的主要区别:

SpringJSR-107Remark
@Cacheable@CacheResult相当相似。 @CacheResult 可以缓存特定的异常,不管缓存的内容是什么都强制执行方法。
@CachePut@CachePut当 Spring 使用方法调用的结果更新缓存时,JCache 要求将其作为参数传递,并使用 @CacheValue 进行注释。由于这种差异,JCache 允许在实际方法调用之前或之后更新缓存。
@CacheEvict@CacheRemove相当相似。当方法调用导致异常时,@CacheRemove 支持条件驱逐。
@CacheEvict(allEntries=true)@CacheRemoveAll请参阅@CacheRemove。
@CacheConfig@CacheDefaults让您以类似的方式配置相同的概念。

JCache 有 javax.cache.annotation.CacheResolver 的概念,它与 Spring 的 CacheResolver 接口相同,只是 JCache 只支持单个缓存。默认情况下,一个简单的实现会根据注释上声明的名称检索要使用的缓存。需要注意的是,如果注解上没有指定缓存名称,则会自动生成一个默认值。有关更多信息,请参阅 @CacheResult#cacheName() 的 javadoc。

CacheResolver 实例由 CacheResolverFactory 检索。可以为每个缓存操作自定义工厂,如以下示例所示:

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

为此操作自定义工厂。

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

键由 javax.cache.annotation.CacheKeyGenerator 生成,其作用与 Spring 的 KeyGenerator 相同。默认情况下,所有方法参数都会被考虑在内,除非至少有一个参数用@CacheKey 注释。这类似于 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 可以管理注释方法抛出的异常。这可以防止更新缓存,但它也可以缓存异常作为失败的指示器,而不是再次调用该方法。假设如果 ISBN 的结构无效,则抛出 InvalidIsbnNotFoundException。这是一个永久性的失败(没有书可以用这样的参数检索)。以下缓存异常,以便使用相同的无效 ISBN 的进一步调用直接抛出缓存的异常,而不是再次调用该方法:

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

2.2、启用 JSR-107 支持

您无需执行任何特定操作即可启用 JSR-107 支持以及 Spring 的声明性注释支持。如果类路径中存在 JSR-107 API 和 spring-context-support 模块,@EnableCaching 和 cache:annotation-driven XML 元素都会自动启用 JCache 支持。

根据您的用例,选择基本上是您的。您甚至可以通过在某些服务上使用 JSR-107 API 并在其他服务上使用 Spring 自己的注释来混合和匹配服务。但是,如果这些服务影响相同的缓存,您应该使用一致且相同的key生成实现。

3、声明式基于 XML 的缓存

如果注释不是一个选项(可能是因为无法访问源代码或没有外部代码),您可以使用 XML 进行声明式缓存。因此,您可以在外部指定目标方法和缓存指令,而不是注释缓存方法(类似于声明式事务管理建议)。上一节中的示例可以转换为以下示例:

<!-- 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 -->

在前面的配置中,bookService 是可缓存的。要应用的缓存语义封装在 cache:advice 定义中,这导致使用 findBooks 方法将数据放入缓存,使用 loadBooks 方法清除数据。这两个定义都适用于book缓存。

aop:config 定义通过使用 AspectJ 切入点表达式将缓存建议应用于程序中的适当点(更多信息可在 Aspect Oriented Programming with Spring 中获得)。在前面的示例中,考虑了 BookService 中的所有方法,并将缓存建议应用于它们。

声明性 XML 缓存支持所有基于注释的模型,因此在两者之间移动应该相当容易。此外,两者都可以在同一个应用程序中使用。基于 XML 的方法不涉及目标代码。但是,它本质上更加冗长。在处理具有针对缓存的重载方法的类时,识别正确的方法确实需要额外的努力,因为方法参数不是一个好的鉴别器。在这些情况下,您可以使用 AspectJ 切入点来挑选目标方法并应用适当的缓存功能。然而,通过 XML,更容易应用包或组或接口范围的缓存(同样,由于 AspectJ 切入点)并创建类似模板的定义(就像我们在前面的示例中所做的那样,通过缓存定义目标缓存:definitions 缓存属性)。

4、配置缓存存储

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

4.1、JDK 基于 ConcurrentMap 的缓存

基于 JDK 的缓存实现位于 org.springframework.cache.concurrent 包下。它允许您使用 ConcurrentHashMap 作为后备缓存存储。以下示例显示了如何配置两个缓存:

<!-- 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。请注意,名称是直接为每个缓存配置的。

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

4.2、基于ehcache的缓存

Ehcache 2.x 实现位于 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 库(通过 ehcache bean),然后将其连接到专用的 CacheManager 实现中。请注意,整个 Ehcache 特定的配置是从 ehcache.xml 中读取的。

4.3、Caffeine缓存

Caffeine 是 Java 8 对 Guava 缓存的重写,其实现位于 org.springframework.cache.caffeine 包中,并提供对 Caffeine 多项功能的访问。

以下示例配置了一个按需创建缓存的 CacheManager:

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

您还可以提供要显式使用的缓存。在这种情况下,只有那些由经理提供。以下示例显示了如何执行此操作:

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

Caffeine CacheManager 还支持自定义 Caffeine 和 CacheLoader。有关这些的更多信息,请参阅Caffeine文档。

4.4、基于 GemFire 的缓存

GemFire 是一个面向内存、磁盘支持、弹性可扩展、持续可用、活跃(具有内置的基于模式的订阅通知)、全局复制的数据库,并提供功能齐全的边缘缓存。有关如何将 GemFire 用作 CacheManager(以及更多)的更多信息,请参阅 Spring Data GemFire 参考文档。

4.5、JSR-107 缓存

Spring 的缓存抽象也可以使用符合 JSR-107 的缓存。 JCache 实现位于 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" .../>

4.6、在没有后备存储的情况下处理缓存

有时,在切换环境或进行测试时,您可能有缓存声明而没有配置实际的后备缓存。由于这是一个无效的配置,在运行时会抛出一个异常,因为缓存基础设施无法找到合适的存储。在这种情况下,与其删除缓存声明(这可能证明很乏味),不如连接一个不执行缓存的简单虚拟缓存⟩——也就是说,它强制每次都调用缓存的方法。以下示例显示了如何执行此操作:

<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 标志,为所有未被配置的缓存管理器处理的定义添加一个空操作缓存。也就是说,在 jdkCache 或 gemfireCache(在示例的前面配置)中找不到的每个缓存定义都由无操作缓存处理,它不存储任何信息,导致每次都调用目标方法。

5、插入不同的后端缓存

显然,有很多缓存产品可以用作后备存储。对于那些不支持 JSR-107 的,您需要提供 CacheManager 和 Cache 实现。这听起来可能比实际更难,因为在实践中,类往往是简单的适配器,将缓存抽象框架映射到存储 API 之上,就像 ehcache 类所做的那样。大多数 CacheManager 类都可以使用 org.springframework.cache.support 包中的类(例如 AbstractCacheManager 负责样板代码,只留下实际的映射来完成)。

6、如何设置 TTL/TTI/Eviction policy/XXX 功能?

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