缓存抽象: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 确定。明显的默认值是分别根据 cacheNameexceptionCacheName 属性解析要使用的缓存。但是,也可以自定义每个操作要使用的工厂。

@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 具有特殊的异常处理,以防止在注释方法抛出与注解上指定的过滤器匹配的异常时发生驱逐。

其他功能

CacheDefaults

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

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

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

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

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

启用 JSR-107 支持

JCache 支持的实现使用了我们自己的 CacheCacheManager 抽象,这意味着您可以使用现有的 CacheManager 基础设施,并且仍然可以使用标准注解!

要启用 Spring 缓存注解的支持,您习惯于使用 @EnableCaching<cache:annotation-driven/> xml 元素,例如

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

    ...
}

那么,将标准注解的支持融入其中需要做些什么呢?嗯,没多少。如果您尚未添加,只需将 JCache API 和 spring-context-support 模块添加到您的类路径中,您就可以开始了。

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

总结

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

想尝试一下吗?获取 Spring 4.1 的 夜间快照版本,并将 javax.cache:cache-api:1.0.0org.springframework:spring-context-support:4.1.0.BUILD-SNAPSHOT 依赖项添加到您的项目中。文档 也已更新,如果您需要更多详细信息。

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

获取 Spring 电子邮件简报

通过 Spring 电子邮件简报保持联系

订阅

领先一步

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

了解更多

获取支持

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

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部