领先一步
VMware 提供培训和认证,助您加速进步。
了解更多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 |
让我们先来看每个注解以及它们的使用方法。这将是一个更好地理解它们支持您已经熟悉的 Spring 注解以及更重要的是这些注解带来的 **新** 功能的机会。
@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 的方式非常相似。
虽然注解名称相同,但 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 操作的发生。
最后,可以控制缓存是在调用带注解的方法之前还是之后更新。当然,如果在此之前更新,则不会进行异常处理。
这些与 @CacheEvict 和 @CacheEvict(allEntries = true) 分别非常相似。@CacheRemove 具有特殊的异常处理机制,可以防止在带注解的方法抛出与注解上指定的过滤器匹配的异常时进行失效。
@CacheDefaults 是一个类级别的注解,它允许您**共享**类上定义的任何缓存操作的通用设置。这些设置包括:
CacheResolverFactoryCacheKeyGenerator在下面的示例中,任何与缓存相关的操作都将使用 books 缓存。
@CacheDefaults(cacheName = "books")
public class BookRepositoryImpl implements BookRepository {
@CacheResult
public Book findBook(@CacheKey ISBN isbn) { ... }
}
JCache 支持的实现使用了我们自己的 Cache 和 CacheManager 抽象,这意味着您可以使用现有的 CacheManager 基础设施,同时仍然使用标准注解!
要启用 Spring 缓存注解的支持,您通常会使用 @EnableCaching 或 <cache:annotation-driven/> XML 元素,例如:
@Configuration
@EnableCaching
public class AppConfig {
@Bean
public CacheManager cacheManager() { ...}
...
}
那么,要将标准注解的支持引入混合使用需要什么呢?不多。只需在类路径中添加 JCache API 和 spring-context-support 模块(如果尚未添加),您就已准备就绪。
现有的基础设施实际上会检查 JCache API 的存在情况,当发现 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 注解如何影响我们自己的支持以及其他一些与缓存相关的改进。