缓存抽象:JCache (JSR-107) 注解支持

工程 | Stéphane Nicoll | 2014 年 4 月 14 日 | ...

Spring 的缓存抽象自 Spring 3.1 起可用,是时候对其多加关注了。在本文中,我想向您介绍该领域的主要改进,即 JCache (JSR-107) 注解支持。

您可能听说过,JSR-107 最终确定,距离最初的提案已有 13 年。对于熟悉 Spring 缓存注解的人来说,下表描述了 Spring 注解与 JSR-107 对应注解之间的映射关系

Spring JSR-107
@Cacheable @CacheResult
@CachePut @CachePut
@CacheEvict @CacheRemove
@CacheEvict(allEntries=true) @CacheRemoveAll

JCache 注解

让我们先来看看每个注解并描述它们的用法。这将有助于更好地理解它们在您习惯使用的 Spring 注解基础上提供了哪些支持,以及更重要的是这些注解带来的**新**特性。

@CacheResult

`@CacheResult` 与 `@Cacheable` 非常相似,下面是使用 `@CacheResult` 注解重写原始示例的代码

@CacheResult(cacheName = "books")
public Book findBook(ISBN isbn) {...}

键生成可以使用 `CacheKeyGenerator` 接口进行自定义。如果未指定具体实现,根据规范,默认实现会获取所有参数,除非一个或多个参数被 `@CacheKey` 注解标注,在这种情况下只使用这些参数。假设上面的方法现在需要一个不应包含在键中的额外属性,这就是我们在 JCache 中编写它的方式

@CacheResult(cacheName = "book")
public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse) { ... }

`@CacheResult` 引入了*异常*缓存的概念:每当方法执行失败时,可以*缓存*抛出的异常,以防止再次调用该方法。假设如果 ISBN 的结构无效,会抛出 `InvalidIsbnNotFoundException`。这是一个永久性故障,使用此类参数永远无法检索到图书。以下代码缓存了该异常,以便使用相同无效 ISBN 的后续调用直接抛出缓存的异常,而不是再次调用该方法。

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

当然,盲目地抛出缓存的异常可能会令人非常困惑,因为调用堆栈可能与当前的调用上下文不匹配。我们尽力通过使用一致的调用堆栈复制异常来确保堆栈跟踪匹配。

JCache 有一个很酷的 `CacheResolver` 概念,它允许在运行时解析要使用的缓存。由于 JCache 支持常规缓存和异常缓存,要使用的 `CacheResolver` 实例由 `CacheResolverFactory` 确定。显而易见的默认值是分别基于 `cacheName` 和 `exceptionCacheName` 属性解析要使用的缓存。但是,也可以自定义每次操作使用的工厂。

@CacheResult(cacheName = "books", cacheResolverFactory = MyFactory.class)
public Book findBook(@CacheKey ISBN isbn) { ... }

最后,`@CacheResult` 有一个 `skipGet` 属性,启用后无论缓存状态如何,都会*始终*调用该方法。这实际上与我们自己使用 `@CachePut` 非常相似。

@CachePut

尽管这些注解名称相同,但在 JCache 中的语义却相当不同。对我们的图书进行简单更新可以这样编写

@CachePut(value = "books", key = "#p0")
public Book update(ISBN isbn, Book updatedBook) { ... }

而 JCache 会要求您这样编写

@CachePut(cacheName = "books")
public void update(ISBN isbn, @CacheValue Book updatedBook) { ... }

请注意,即使 `updatedBook` 不应作为键的一部分,我们也不必在第一个参数上添加 `@CacheKey`。这是因为被 `@CacheValue` 注解的参数会自动从键生成中排除。

与 `@CacheResult` 一样,`@CachePut` 允许处理方法执行期间抛出的任何异常,如果抛出的异常与注解上指定的过滤器匹配,则阻止 put 操作发生。

最后,可以控制缓存是在被注解方法调用之前还是之后更新。当然,如果在之前更新,则不会进行异常处理。

@CacheRemove 和 @CacheRemoveAll

它们分别与 `@CacheEvict` 和 `@CacheEvict(allEntries = true)` 非常相似。`@CacheRemove` 有特殊的异常处理机制,如果被注解方法抛出的异常与注解上指定的过滤器匹配,则阻止逐出(eviction)操作。

其他特性

CacheDefaults

`@CacheDefaults` 是一个类级别的注解,允许您在类上定义的任何缓存操作中*共享*通用设置。这些设置包括

  • 缓存的名称
  • 自定义的 `CacheResolverFactory`
  • 自定义的 `CacheKeyGenerator`

在下面的示例中,任何与缓存相关的操作都将使用 `books` 缓存

@CacheDefaults(cacheName = "books")
public class BookRepositoryImpl implements BookRepository {

    @CacheResult
    public Book findBook(@CacheKey ISBN isbn) { ... }
}

启用 JSR-107 支持

JCache 支持的实现使用了我们自己的 `Cache` 和 `CacheManager` 抽象,这意味着您可以使用现有的 `CacheManager` 基础设施,同时使用标准注解!

要启用 Spring 缓存注解的支持,您习惯使用 `@EnableCaching` 或 `` XML 元素,例如这样

@Configuration
@EnableCaching
public class AppConfig {
    @Bean
    public CacheManager cacheManager() { ...}

    ...
}

那么,要将标准注解的支持引入其中需要什么呢?其实不多。如果您尚未添加,只需在类路径中添加 JCache API 和 `spring-context-support` 模块即可。

现有的基础设施实际上会查找 JCache API 的存在,当与 Spring 的 JCache 支持一起找到时,它也会配置必要的基础设施来支持标准注解。

#总结

长话短说,如果您已经在使​​用 Spring 的缓存抽象并想尝试标准注解,只需向您的项目添加另外两个依赖项即可开始。

想试试吗?获取 Spring 4.1 的夜间 SNAPSHOT 构建版本,并将 `javax.cache:cache-api:1.0.0` 和 `org.springframework:spring-context-support:4.1.0.BUILD-SNAPSHOT` 依赖项添加到您的项目中。文档也已更新,以防您需要更多详情。

在下一篇文章中,我将介绍支持 JSR-107 注解如何影响我们自己的支持以及其他一些与缓存相关的改进。

订阅 Spring 新闻通讯

订阅 Spring 新闻通讯,保持联系

订阅

保持领先

VMware 提供培训和认证,助您加速进步。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅即可提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部