本文是缓存系列第三篇,前两篇分别介绍了 Guava 和 JetCache。
前两篇我们讲了 Guava 和 JetCache,它们都是缓存的具体实现,今天给大家分析一下 Spring 框架本身对这些缓存具体实现的支持和融合。使用 Spring Cache 将大大的减少我们的Spring项目中缓存使用的复杂度,提高代码可读性。本文将从以下几个方面来认识Spring Cache框架。
背景
SpringCache 产生的背景其实与Spring产生的背景有点类似。由于 Java EE 系统框架臃肿、低效,代码可观性低,对象创建和依赖关系复杂, Spring 框架出来了,目前基本上所有的Java后台项目都离不开 Spring 或 SpringBoot (对 Spring 的进一步简化)。现在项目面临高并发的问题越来越多,各类缓存的应用也增多,那么在通用的 Spring 框架上,就需要有一种更加便捷简单的方式,来完成缓存的支持,就这样 SpringCache就出现了。
不过首先我们需要明白的一点是,SpringCache 并非某一种 Cache 实现的技术,SpringCache 是一种缓存实现的通用技术,基于 Spring 提供的 Cache 框架,让开发者更容易将自己的缓存实现高效便捷的嵌入到自己的项目中。当然,SpringCache 也提供了本身的简单实现 NoOpCacheManager、ConcurrentMapCacheManager 等。通过 SpringCache,可以快速嵌入自己的Cache实现。
用法
源码已分享至Github:https://github.com/zhuzhenke/common-caches
注意点:
- 开启 EnableCaching 注解,默认没有开启 Cache。
- 配置 CacheManager。
@Bean @Qualifier("concurrentMapCacheManager") @Primary ConcurrentMapCacheManager concurrentMapCacheManager() { return new ConcurrentMapCacheManager(); }
这里使用了 @Primary 和 @Qualifier 注解,@Qualifier 注解是给这个 Bean 加一个名字,用于同一个接口 Bean 的多个实现时,指定当前 Bean 的名字,也就意味着 CacheManager 可以配置多个,并且在不同的方法场景下使用。@Primary 注解是当接口 Bean 有多个时,优先注入当前 Bean 。
现在拿 CategoryService 实现来分析。
public class CategoryService { @Caching(evict = {@CacheEvict(value = CategoryCacheConstants.CATEGORY_DOMAIN, key = "#category.getCategoryCacheKey()", beforeInvocation = true)}) public int add(Category category) { System.out.println("模拟进行数据库交互操作......"); System.out.println("Cache became invalid,value:" + CategoryCacheConstants.CATEGORY_DOMAIN + ",key:" + category.getCategoryCacheKey()); return 1; } @Caching(evict = {@CacheEvict(value = CategoryCacheConstants.CATEGORY_DOMAIN, key = "#category.getCategoryCacheKey()", beforeInvocation = true)}) public int delete(Category category) { System.out.println("模拟进行数据库交互操作......"); System.out.println("Cache became invalid,value:" + CategoryCacheConstants.CATEGORY_DOMAIN + ",key:" + category.getCategoryCacheKey()); return 0; } @Caching(evict = {@CacheEvict(value = CategoryCacheConstants.CATEGORY_DOMAIN, key = "#category.getCategoryCacheKey()")}) public int update(Category category) { System.out.println("模拟进行数据库交互操作......"); System.out.println("Cache updated,value:" + CategoryCacheConstants.CATEGORY_DOMAIN + ",key:" + category.getCategoryCacheKey() + ",category:" + category); return 1; } @Cacheable(value = CategoryCacheConstants.CATEGORY_DOMAIN, key = "#category.getCategoryCacheKey()") public Category get(Category category) { System.out.println("模拟进行数据库交互操作......"); Category result = new Category(); result.setCateId(category.getCateId()); result.setCateName(category.getCateId() + "CateName"); result.setParentId(category.getCateId() - 10); return result; } }
CategoryService 通过对 category 对象的数据库增删改查,模拟缓存失效和缓存增加的结果。使用非常简便,把注解加在方法上,则可以达到缓存的生效和失效方案。
深入源码
源码分析我们分为几个方面一步一步解释其中的实现原理和实现细节。源码基于 Spring 4.3.7.RELEASE 分析。
发现
SpringCache 在方法上使用注解发挥缓存的作用,缓存的发现是基于 AOP 的 PointCut 和 MethodMatcher 通过在注入的 class 中找到每个方