Spring-5-精品翻译:缓存抽象(Cache-Abstraction)

32.1 介绍

Spring 框架从 3.1 开始,对 Spring 应用程序提供了透明式添加缓存的支持。和事务支持同样,抽象缓存容许一致地使用各类缓存解决方案,并对代码的影响最小。html

从 4.1 版本开始,缓存抽象支持了 JSR-107 注释和更多自定义选项,从而获得了显著的改进。java

32.2 缓存抽象

缓存(Cache) vs 缓冲区(Buffer)

缓存和缓冲区两个术语每每能够互换着使用。但注意,它们表明着不一样的东西。
缓冲区是做用于快和慢速实体之间的中间临时存储。
一块缓冲区必须等待其余并影响性能,经过容许一次性移动整个数据块而不是小块来缓解。数据从缓冲区读写只有一次。所以缓冲区对至少一方是可见的。
另外一方面,缓存根据定义是隐性的,双方不会知道缓存的发生。它提升了性能,但容许以快速的方式屡次读取相同的数据。

想了解更多这两者之间的差别,见:https://en.wikipedia.org/wiki/Cache_(computing)#The_difference_between_buffer_and_cache

核心上,抽象将缓存做用于 Java 方法上,基于缓存中的可用信息,能够减小方法的执行次数。也就是说,每次目标方法的调用时,抽象使用缓存行为来检查执行方法,检查执行方法是否给定了缓存的执行参数:若是有,则返回缓存结果,不执行具体方法;若是没有,则执行方法,并将结果缓存后,返回给用户。以便于下次调用方法时,直接返回缓存的结果。这样,只要给定缓存执行参数,在复杂的方法(不管是 CPU 或者 IO 相关)只须要执行一次,就能够获得结果,并利用缓存可重复使用结果,而没必要再次执行该方法。另外,缓存逻辑能够被透明地调用,不会对调用者形成任何的困扰。git

显然,这种方法只适用于为某个给定输入(或参数)返回相同输出(结果),不管执行多少次。

抽象提供的其余缓存相关操做,好比更新缓存内容或者删除其中一条缓存。若是在应用程序过程当中,发生了变化的数据须要缓存,那这些功能会颇有用。github

好比 Spring 框架其余服务同样,缓存服务是一种抽象(不是缓存的实现),而且须要使用实际的存储器来存储缓存数据。也就是说,抽象可以使开发人员没必要编写缓存逻辑,但它没有提供缓存的存储器。这个抽象是由 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 接口实现的。算法

有些抽象的实现是开箱即用的:基于 JDK java.util.concurrent.ConcurrentMap 缓存实现,Ehcache 2.x,Gemfire cache, Caffeine 和 JSR-107 缓存(例如 Ehcache 3.x)。有关缓存存储/提供的更多信息,请参见 32.7 节 《Plugging-in different back-end caches》。spring

缓存抽象没有特别处理多线程和多进程环境,由于这些功能由缓存实现来处理...

若是是多进程环境(即部署在多个节点上的应用程序),则须要相应的配置程序提供缓存。根据使用状况,几个节点上相同数据的副本可能足够多了,但若是在应用程序过程当中更改了数据,则须要启动其余传播机制,进行同步缓存数据。数据库

缓存一个特定的对象是典型的缓存交互 get-if-not-found-then-proceed-and-put-finally 代码块:不需应用任何锁,而且几个线程同时尝试加载相同的对象。一样适用于回收,若是多个线程同时更新或者回收数据,则可能会使用过期的数据。某些缓存提供者在该领域提供高级功能,请参考您正在使用的缓存提供者的更多高级功能的详细信息。express

要使用缓存抽象,开发人员须要注意两个方面:后端

  • 缓存声明 - 标志缓存的方法及缓存策略
  • 缓存配置 - 数据读写的缓存数据库

32.3 基于声明式注解的缓存

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

  • @Cacheable 触发缓存机制
  • @CacheEvict 触发缓存回收
  • @CachePut 更新缓存,而不会影响方法的执行
  • @Caching 组合多个缓存操做到一个方法
  • @CacheConfig 类级别共享系诶常见的缓存相关配置

下面,让咱们仔细看看每一个注释。

32.3.1 @Cacheable 注解

顾名思义,@Cacheable 用于标识可缓存的方法 - 即须要将结果存储到缓存中的方法,以便于再一次调用(具备相同的输入参数)时返回缓存结果,而无需执行该方法。在最简单的形式中,注解声明须要定义与注解方法相关联的缓存名称:

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

在上面的代码中,findBook 与名为 books 的缓存相关。每次调用该方法时,都会检查缓存以查看调用是否已经被执行,而且没必要重复。而在大多数状况下,只有一个缓存被声明,注解容许指定多个名称,以便使用多个缓存。在这种状况下,执行该方法以前将检查每一个高速缓存 - 若是至少有一个缓存被命中,则返回缓存相关的值:

注意:即便没有实际执行缓存方法,全部其余不包含该值的缓存也将被更新。
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}

默认键生成

缓存的本质是键值对存储,因此每次调用缓存方法都会转换做用于缓存访问合适的键。开箱即用,缓存抽象使用基于如下算法的简单 KeyGenerator:

  • 若是没有参数,返回 SimpleKey.EMPTY
  • 若是只有一个参数,返回该实例
  • 若是大于一个参数,返回一个包含全部参数的 SimpleKey

这种算法对大多数用例很适用,只要参数具备天然键并实现了有效的 hashCode()equals() 方法。若是不是这样,策略就须要改变。
要提供不一样的默认密钥生成器,须要实现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 参数影响了该 findBook 方法,但对缓存没有任何用处。更进一步,若是二者之中只有一个是重要的,另外一个不重要呢?

这种状况下,@Cacheable 注解只容许用户经过键属性指定 key 的生成方式。开发人员可使用 SpEL 来选择须要的参数(或其嵌套属性),执行参数设置调用任何方法,无需编写任何代码或者实现人任何接口。这是默认键生成器推荐的方法,由于方法在代码库的增加下,会有彻底不一样的方法实现。而默认策略可能适用于某些方法,并非适用于全部的方法。

这里是 SpEL 声明的一些示例 - 若是你不熟悉它,查阅Chapter 6, Spring Expression Language (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)

上面代码片断显示了选择某个参数,或参数的某个属性值或任意(静态)方法,如此方便操做。

若是生成键的算法太具体或者须要共享,能够操做中定义一个自定定义的 keyGenerator。为此,请指定要使用的 KeyGenerator Bean 实现的名称:

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

默认缓存解析

开箱即用,缓存抽象使用一个简单的 CacheResolver,在 CacheManager 能够配置操做级别来检索缓存。

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

自定义缓存解析

默认缓存解析适用于使用单个 CacheManager 而且应用在不须要复杂缓存解析的应用程序。

对于使用多个缓存管理器的应用,能够为每一个操做设置一个 cacheManager:

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

也能够彻底以与键生成相似的方式来替换 CacheResolver。每一个缓存操做都要求缓存解析,基于运行时参数的缓存解析:

@Cacheable(cacheResolver="runtimeCacheResolver")
public Book findBook(ISBN isbn) {...}
自 Spring 4.1 之后,缓存注解的属性值是没必要要的,由于 CacheResolver 能够提供该特定的信息,不管注解的内容是什么。与 key 和 keyGenerator 相似,cacheManager 和 cacheResolver 参数是互斥的,而且指定二者一样的操做会致使异常,由于 CacheResolver 的实现将忽略自定义的 CacheManager。这是你不但愿的。

同步缓存

在多线程环境中,某些操做可能会致使同时引用相同的参数(一般在启动时)。默认状况下,缓存抽象不会锁定任何对象,一样的对象可能会被计算好几回,从而达不到缓存的目的。

对于这些特熟状况,sync 属性可用于指示底层缓存提供程序在计算该值时锁定缓存对象。所以,只有一个线程将忙于计算值,而其余线程会被阻塞,直到该缓存对象被更新为止。

@Cacheable(cacheNames="foos", sync="true")
public Foo executeExpensiveOperation(String id) {...}
这是可选功能,可能你是用的缓存库不支持它。由核心框架提供的全部 CacheManager 都会实现并支持它。翻阅缓存提供商文档能够了解更多详细信息。

条件缓存

有时,一种方法可能不适合缓存(例如,它可能取决于给定的参数)。缓存注解经过条件参数支持这样的功能,采用使用 SpEL 表达式的 condition 参数表示。若是是 true,则缓存方法,若是是 false,则不缓存,不管缓存中有什么值或者使用了哪些参数都严格按照规则执行该方法。一个快速入门的例子 - 只有当参数名称长度小于 32 的时候,才会缓存下面方法:

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

另外,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)

注意:结果仍然是 Book 不是 Optional

缓存 SpEL 上下文

每一个 SpEL 表达式都会有求值上下文。除了构建参数,框架提供了专门的缓存相关元数据,好比参数名称。下表列出了可用于上下文的项目,所以可使用他们进行键和条件的计算:

表 32.1 缓存 SpEL 元数据

参数名 用处 描述 例子
methodName root object 被调用的方法名 #root.methodName
method root object 被调用的方法 #root.method.name
target root object 被调用的对象 #root.target
targetClass root object 被调用的类 #root.targetClass
args root object 被调用的的类目标参数 #root.args[0]
caches root object 当前方法执行的缓存列表 #root.caches[0].name
argument name evaluation context 任何方法参数的名称 #iban#a0 (也可使用 #p0 或者 #p<#arg>)
result evaluation context 方法调用的结果(缓存的值) #result

32.3.2 @CachePut 注解

须要缓存更新但不影响方法执行的状况,可使用 @CachePut 注解。也就是说,该方法始终执行,并将其结果放入缓存中(根据 @CachePut 选项)。它支持与 @Cacheable 相同的选项,适用于缓存而不是方法流程优化:

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
对同一个方法同时使用 @CachePut@Cacheable 注解一般是不推荐的。由于它们有不一样的行为。后者经过使用缓存并跳过方法执行,前者强制执行方法并进行缓存更新。这会致使意想不到的行为,除了具体的场景(例如须要排除条件的注解),应该避免这样的声明方式。还要注意,这样的条件不该该依赖于结果对象(#result 对象),由于结果对象应该在前面被验证后排除。

32.3.3 @CacheEvict 注解

缓存抽象不只仅缓存更多数据,还能够回收缓存。这个过程对于从缓存中删除旧数据或者未使用的数据很是有用。与 @Cacheable 相反, 注解 @CacheEvict 划分了回收缓存的方法,即做为从缓存中删除数据的触发器方法。@CacheEvict 须要指定一个(或者多个)执行动做,而且容许自定义缓存和键解析或条件被指定。但有一个额外的参数 allEntries,能够指示是否全部对象都要被收回,仍是一个对象(取决于key)。

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

当整个缓存区须要被收回时,这个选项就会派上用场 - 而不是回收每一个对象(当不起做用时会须要很长的响应时间),因此对象会在一个操做中被删除,如上所示。注意,框架将忽略此场景下指定的任何key,由于它不适用(整个缓存收回,不只仅是一个条目被收回)。

还能够指出发生缓存回收是在默认以后仍是在经过 beforeInvocation 属性执行方法后。前者提供与其余注解相同的语义 - 一旦方法成功完成,就执行缓存上的动做(这种状况是回收)。若是方法不执行(由于它可能会被缓存)或者抛出异常,则不会发生回收。后者(beforeInvocation = ture)会致使在调用该方法以前发生回收 - 在驱逐不须要与方法结果相关的状况下,这是有用的。

重要的是,void 方法能够和 @CacheEvict 一块儿使用 - 因为方法做为触发器,返回值会被忽略(由于他们不与缓存交互) - @Cacheable 就不是这样的,它添加 / 更新数据到缓存时,须要一个结果。

32.3.4 @Caching 注解

另外状况下,须要指定想同类型的多个注解,例如 @CacheEvict@CachePut 须要被指定。好比由于不一样缓存之间的条件或者键表达式不一样。@Caching 容许在同一个方法上使用多个嵌套的 @Cacheable@CachePut@CacheEvict

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

32.3.5 @CacheConfig 注解

到目前为止,咱们已经看到缓存操做提供了许多定制选项,这些选项能够在操做的基础上进行设置。 可是,一些自定义选项可能很麻烦,能够配置是否适用于该类的全部操做。 例如,指定用于该类的每一个缓存操做的高速缓存的名称能够被单个类级别定义所替代。 这是@CacheConfig发挥做用的地方。

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

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

@CacheConfig 是一个类级别的注解,能够共享缓存名称。自定义 KeyGenerator,自定义 CacheManager 和自定义 CacheResolver。该注解在类上不会操做任何缓存。

操做级别上的自定义会覆盖 @CacheConfig 的自定义。所以,每一个缓存操做都会有三个级别的定义:

  • 全局配置,可用于 CacheManagerKeyGenerator
  • 类级别,用 @CacheConfig
  • 操做级别层面

@EnableCaching 注解

重点注意的是,即便声明缓存注解也不会主动触发动做 - 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 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 注解容许指定多种选项,这些选项经过 AOP 将缓存行为添加到应用程序的方式。该配置与 @Transactional 注解相似:

Java 高级自定义配置须要实现 CachingConfigurer。更多详细信息,参考 javadoc。

表 32.2. 缓存注解设置

XML 属性 注解属性 默认 描述
cache-manager N/A(参阅 CachingConfigurer javadocs) cacheManager 要使用的缓存管理器的名称。 使用该缓存管理器(或“cacheManager”未设置)将在幕后初始化默认CacheResolver。 要更精细地管理缓存解析度,请考虑设置“缓存解析器”属性。
cache-resolver N/A(参阅 CachingConfigurer javadocs) 配置 cacheManager SimpleCacheResolver 要用于解析后备缓存的CacheResolver的bean名称。 此属性不是必需的,只须要指定为“cache-manager”属性的替代方法。
key-generator N/A(参阅 CachingConfigurer javadocs) SimpleKeyGenerator 要使用的自定义键生成器的名称。
error-handler N/A(参阅 CachingConfigurer javadocs) SimpleCacheErrorHandler 要使用的自定义缓存错误处理程序的名称。 默认状况下,在缓存相关操做期间抛出任何异常抛出在客户端。
mode mode proxy 默认模式“代理”使用Spring的AOP框架处理带注释的bean(如下代理语义,如上所述,适用于仅经过代理进行的方法调用)。 替代模式“aspectj”代替使用Spring的AspectJ缓存方面编写受影响的类,修改目标类字节码以应用于任何类型的方法调用。 AspectJ编织须要类路径中的spring-aspects.jar以及启用加载时编织(或编译时编织)。 (有关如何设置加载时编织的详细信息,请参阅 the section called “Spring configuration”
proxy-target-class proxyTargetClass false 仅适用于代理模式。 控制为使用@Cacheable@CacheEvict注释注释的类建立的缓存代理类型。 若是proxy-target-class属性设置为true,则会建立基于类的代理。 若是proxy-target-classfalse,或者若是属性被省略,则会建立基于标准的基于JDK接口的代理。 (有关详细检查不一样代理类型,请参见第Section 7.6, “Proxying mechanisms”
order order Ordered.LOWEST_PRECEDENCE 定义应用于使用@Cacheable@CacheEvict注释的bean的缓存通知的顺序。 (有关与AOP通知的排序有关的规则的更多信息,请参阅 the section called “Advice ordering”。)没有指定的排序意味着AOP子系统肯定建议的顺序。
<cache:annotation-driven /> 只会匹配相对应的应用上下文中定义的 @Cacheable / @CachePut / @CacheEvict / @Cached。若是将 <cache:annotation-driven/> 配置在 DispatcherServletWebApplicationContext ,它只检查控制器中的bean,而不是您的服务。参考 18.2 章节 DispatcherServlet 获取更多信息。
方法可见性和缓存注解
使用代理后,缓存注解应用于公共可见的方法。若是对 protected、private 或 package-visible 方法进行缓存注解,不会引发错误。但注解的方法不会显示缓存配置。若是更改字节码时须要注解非公共方法,请考虑使用 AspectJ(见下文)。
Spring 建议只使用 @Cache* 注解具体的类(或具体的方法),而不是注解接口。能够在接口(或接口方法)上注解 @Cache* ,但只有当使用基于接口的代理时,才能使用它。Java 注解不是用接口继承,若是使用基于类的代理( proxy-target-class="true")或基于代理的 weaving-based(mode="aspectj"),缓存设置没法识别,对象也不会被包装在缓存代理中。

在代理模式(默认状况)中,只有经过代理进入的外部方法调用被截取。实际上,自调用目标对象中调用另外一个目标对象方法的方法在运行时不会致使实际的缓存,即便被调用的方法被 @Cacheable 标志 - 能够考虑 aspectj 模式。此外,代理必须被彻底初始化提供预期行为,所以不该该依赖于初始化的代码功能,即 @PostConstruct

32.3.7 使用自定义注解

自定义注解和 AspectJ
开箱即用,此功能仅使用基于代理的方法,但可以使用 AspectJ 进行一些额外的工做。

spring-aspects 模块仅为标准注解定义了一个切面。若是你定义了本身的注解,那还须要为这些注解定义一个方面。检查 AnnotationCacheAspect 为例。

缓存抽象容许使用不一样的注解识别什么方法触发缓存或者缓存回收。这是很是方便的模板机制,由于不须要重复缓存声明(特别是在指定键和条件),或者在代码库不容许使用外部的导入(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注解,容器也能够在运行时自动选择其声明并了解其含义。 请注意,如上所述,须要启用注解驱动的行为。

32.4 JCache (JSR-107) 注解

Spring Framework 4.1 以来,缓存抽象彻底支持 JCache 标准:即 @CacheResult@CachePut@CacheRemove@CacheRemoveAll 还有 @CacheDefaults@CacheKey@CacheValue 。这些注解被你们正确的使用,体现了缓存在 JSR-107 的实现:缓存抽象的内部实现,并提供了符合规范的默认 CacheResolverKeyGenerator 的实现。换句话说,若是你已经使用了 Spring 缓存抽象,那能够平滑切换到这些标准注解,无需更改缓存存储(或者配置)。

32.4.1 特征总结

对于熟悉 Spring 缓存注解,下面描述了 Spring 注解和 JSR-107 对应的主要区别:

表 32.3. Spring vs JSR-107 缓存注解

Spring JSR-107 备注
@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 只支持单个缓存。默认下,根据注解声明的名称检索要使用的缓存。若是没有指定缓存的名称,则会自动生成默认值。更多信息能够查看 javadoc 的 @CacheResult#cacheName()

CacheResolverFactory 检索出 CacheResolver 实例。每一个缓存操做均可以自定义工厂:

@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class)
public Book findBook(ISBN isbn)
注意
对于全部引用的类,Spring 会找到具体指定类型的 Bean。若是存在多个匹配,会建立一个新实例,并能够正常的 Bean 生命周期回调(如依赖注入)

键由 javax.cache.annotation.CacheKeyGenerator 生成,和 Spring 的 KeyGenerator 能达到相同的目标。默认状况下,全部方法参数都会被考虑,除非至少有一个参数被注解为 @CacheKey。这和 Spring 的自定义键生成相似。例如,下面代码操做是相同的,一个使用 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 管理注解方法抛出的异常:能够防止缓存更新,但将异常做为故障的标志,而不是再次调用该方法。假设抛出 InvalidIsbnNotFoundException 异常,那么 ISBN 的结构是无效的。若是这是一个永久的失败,没有任何书会被查询出来。如下的缓存异常,以使具备无效的 ISBN 进一步直接抛出缓存的异常,而不是再次调用该方法。

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

32.4.2 启用 JSR-107 支持

不须要特殊处理,去支持 JSR-107 和 Spring 的声明式注解。JSR-17 API 和 spring-context-support 模块在类路径下,@EnableCachingcache:annotation-driven 二者会被启用。

根据你的具体案例选择须要的。你还可使用 JSR-107 API 和其余使用 Spring 本身的注解来匹配服务。注意,若是这些服务影响相同的缓存,则使用一致的键生成实现。

32.5 缓存声明式 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

32.6 配置缓存存储

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

32.6.1 JDK ConcurrentMap-based Cache

基于JDK的Cache实现位于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建立一个CacheManager,为两个嵌套的ConcurrentMapCache实例命名为defaultbooks。请注意,这些名称是为每一个缓存直接配置的。

因为缓存是由应用程序建立的,所以它必须与其生命周期一致,使其适用于基本用例,测试或简单应用程序。缓存规模好,速度快,但不提供任何管理、持久化能力或驱逐合同。

32.6.2 Ehcache-based Cache(基于Ehcache的缓存)

Ehcache 3.x彻底符合JSR-107标准,不须要专门的支持。

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 bean)中的ehcache库,而后将其链接到专用的CacheManager实现中。请注意,整个ehcache特定的配置是从ehcache.xml读取的。

32.6.3 Caffeine Cache

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

根据须要,配置建立缓存的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 documentation获取更多信息。

32.6.4 GemFire-based Cache(基于GemFire的缓存)

GemFire是面向内存/磁盘支持,弹性可扩展,持续可用,主动(内置基于模式的订阅通知),全局复制数据库,并提供功能齐全的边缘缓存。有关如何使用GemFire做为CacheManager(及更多)的更多信息,请参考 Spring Data GemFire reference documentation

32.6.5 JSR-107 Cache

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" .../>

32.6.6 Dealing with caches without a backing store(处理没有后端存储的缓存)

有时在切换环境或进行测试时,可能会有缓存声明,而不配置实际的后备缓存。因为这是一个无效的配置,所以在运行时将会抛出异常,由于缓存基础结构没法找到合适的存储。在这种状况下,而不是删除缓存声明(这能够证实是乏味的),能够链接一个不执行缓存的简单的虚拟缓存,也就是强制每次执行缓存的方法:

<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连接多个CacheManagers,另外,经过fallbackToNoOpCache标志,添加了一个no op缓存,用于全部不被配置的缓存管理器处理的定义。也就是说,在jdkCachegemfireCache(上面配置)中找不到的每一个缓存定义都将由no op缓存来处理,这不会存储任何致使目标方法每次执行的信息。

32.7 插入不一样的后端缓存


显然,有不少缓存产品能够用做后备存储。要插入它们,须要提供一个CacheManagerCache实现,由于不幸的是没有可用的标准供咱们使用。这听起来比实际上更难听,这些类每每是简单的适配器,将缓存抽象框架映射到存储API的顶部,就像ehcache类能够显示同样。大多数CacheManager类均可以使用org.springframework.cache.support包中的类,例如AbstractCacheManager,其中只须要完成实际的映射便可完成代码。咱们但愿及时提供与Spring集成的库能够填补这个小的配置差距。

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


直接经过您的缓存提供商。缓存抽象是一个抽象而不是缓存实现。您正在使用的解决方案可能支持各类数据策略和其余解决方案不一样的拓扑(例如JDK ConcurrentHashMap),由于缓存中的提取将无济于事,由于不须要后台支持。这样的功能应该经过后台缓存,配置它或经过其本机API直接控制。

文末福利

Java 资料大全 连接:https://pan.baidu.com/s/1pUCC... 密码:b2xc
更多资料: 2020 年 精选阿里 Java、架构、微服务精选资料等,加 v ❤ :qwerdd111

本文由博客一文多发平台 OpenWrite 发布!
相关文章
相关标签/搜索