前言
在上篇文章讲解整合分布式缓存Redis时埋下了一个伏笔:如何让我们的缓存注解支持自定义TTL失效时间呢?
这篇文章本可以不用写,因为其实基于Redis的RedisCacheManager它本身天生就是能够针对不同的Cache配置不同的TTL的。但是我发现有的小伙伴觉得使用得还是不太方便,希望能在使用注解的时候直接控制失效时间,为了帮助解决小伙伴的这个困惑,这就是我书写本文的目的~
Spring Cache与失效时间TTL
首先此处我有必要再次强调一点:Spring Cache抽象本省是并不支持Expire失效时间的设定的,我粗暴的把它归为了Spring Cache抽象的一个设计上的bug,可参考文章:【小家Spring】玩转Spring Cache — @Cacheable/@CachePut/@CacheEvict注解的原理深度剖析和使用
若想在缓存注解上指定失效时间,必须具备如下两个基本条件:
- 缓存实现产品支持Expire失效时间(Ehcache、Redis等几乎所有第三方实现都支持)
- xxxCacheManager管理的xxxCache必须扩展了Expire的实现
因为缓存的k-v键值对具有自动失效的特性实在太重要和太实用了,所以虽然org.springframework.cache.Cache它没有实现Expire,但好在第三方产品对Spring缓存标准实现的时候,大都实现了这个重要的失效策略,比如典型例子:RedisCache。
本文以最为常用的Redis缓存为例,介绍两种控制缓存失效时间的方式。
实现Cache失效时间的两种通用方式
接下来就以Redis Cache为例,介绍两种常用的、通用的管理缓存失效时间的方式。
方式一:使用源生的RedisCacheManager进行集中式控制
由于控制key的失效时间这一块非常的实用和重要,所以其实Spring Data Redis工程早就给与了支持(不管是1.x版本还是2.x版本)。因此话不多说,直接给个例子就非常清晰明了:
1、准备Cache配置:
@EnableCaching // 使用了CacheManager,别忘了开启它 否则无效 @Configuration public class CacheConfig extends CachingConfigurerSupport { @Bean public CacheManager cacheManager() { RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofDays(1)) // 默认没有特殊指定的 .computePrefixWith(cacheName -> "caching:" + cacheName); // 针对不同cacheName,设置不同的过期时间 Map<String, RedisCacheConfiguration> initialCacheConfiguration = new HashMap<String, RedisCacheConfiguration>() {{ put("demoCache", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1))); //1小时 put("demoCar", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10))); // 10分钟 // ... }}; RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory()) .cacheDefaults(defaultCacheConfig) // 默认配置(强烈建议配置上)。 比如动态创建出来的都会走此默认配置 .withInitialCacheConfigurations(initialCacheConfiguration) // 不同cache的个性化配置 .build(); return redisCacheManager; } @Bean public RedisConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); configuration.setHostName("10.102.132.150"); configuration.setPort(6379); configuration.setDatabase(0); LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration); return factory; } @Bean public RedisTemplate<String, String> stringRedisTemplate() { RedisTemplate<String, String> redisTemplate = new StringRedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory()); return redisTemplate; } }
使用示例:
@Service public class CacheDemoServiceImpl implements CacheDemoService { @Caching(cacheable = { @Cacheable(cacheNames = "demoCache", key = "#id + 0"), @Cacheable(cacheNames = "demoCar", key = "#id + 10"), @Cacheable(cacheNames = "demoFsx", key = "#id + 100") }) @Override public Object getFromDB(Integer id) { System.out.println("模拟去db查询~~~" + id); return "hello cache..."; } }
运行单元测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class, CacheConfig.class}) public class TestSpringBean { @Autowired private CacheDemoService cacheDemoService; @Autowired private CacheManager cacheManager; @Test public void test1() { cacheDemoService.getFromDB(1); cacheDemoService.getFromDB(1); System.out.println("----------验证缓存是否生效----------"); Cache cache = cacheManager.getCache("demoCache"); System.out.println(cache); System.out.println(cache.get(1, String.class)); } }
打印结果如下:
模拟去db查询~~~1 ----------验证缓存是否生效---------- org.springframework.data.redis.cache.RedisCache@721eb7df hello cache...
缓存生效。去Redis服务端查看对应的key情况:
眼睛尖的小伙伴可能发现了,只有最后一个key前面有caching前缀,其余两个木有,这是为何呢?
这是我故意留出来的一个小问题,留给小伙伴们自行思考~
由此可见,通过RedisCacheManager完成了对不同的Cache进行了失效时间的定制化配置,达到了我们的目的。
小细节
针对如上的配置,总结如下两点小细节使时需要注意:
- 即使禁用前缀disableKeyPrefix(),也是不会影响对应CacheName的TTL(因为TTL针对的是Cache,而不是key)
- 每个CacheName都可以对应一个RedisCacheConfiguration(它里面有众多属性都可以个性化),若没配置的(比如动态生成的)都走默认配置
Spring提供的在RedisCacheManager来统一管理Cache的TTL,这种集中式的管理其实是我赞同的方式,若让他分散在各个缓存注解上,反而非常不利于后期的维护管理~~~因此这种方式我也是推荐的