Spring Cache
# Spring Cache
自 Spring 3.1 版本以来,Spring 框架支持低侵入的方式向已有 Spring 应用加入缓存特性。与声明式事务类似,声明式缓存 Spring Cache 抽象允许一致的 API 来支持多种不同的缓存解决方案,同时将对代码的影响减少到最小。从 Spring 4.1 开始,Spring 已完整支持 JSR-107 注解和更多的定制选项。
# 区分 Cache 与 Buffer
很多情况下,Buffer(缓冲)与 Cache(缓存)是类似的。然而在表现形式与应用场景上两个的差别还是比较明显的。
传统意义上,Buffer 作为快速实体与慢速实体之间的桥梁。比如:硬盘上的文件数据会先到内存,再被 CPU 加载,内存作为 Buffer 可以减少等待时间,同时利用 Buffer 可将原本小块数据攒成整块一次性交给处理者,可以有效减少 IO。此外,通常至少有一个对象对其可见。
而 Cache 缓存,相对来说是隐藏的,对于访问者与被访问者来说应该是隐藏的,好的程序设计可以让使用者对缓存无感知,同时它还可以提高性能,允许应用多次、快速的读取缓存数据。
# JSR-107 与 Spring 自带注解对比
JSR-107(JCache)
标准化:提供统一的 Java 缓存 API,使代码与具体缓存实现(如 Ehcache、Hazelcast)解耦。
注解驱动:通过简洁的注解(如 @CacheResult)声明缓存行为。
灵活性:支持缓存加载、过期策略、监听器等高级特性。
Spring | JSR-107 | Remark |
---|---|---|
Cacheable | CacheResult | Fairly similar. BacheResult can cache specific exceptions and force the execution of the method regardless of the content of the cache. |
CachePut | CachePut | While Spring updates the cache with the result of the method invocation. JCache requires that it be passed it as an argument that is annotated with QCacnevalve.Due to this difference,JCache allows updating the cache before or after the actual method invocation. |
CacheEvict | CacheRenove | Fairly similar. (acacheRenovo $supports conditional eviction when the method invocation results in an exception. |
@cachecrict(allentrie s-true) | CacheRemoveAll | Seecacheremove. |
CacheConfig | CacheDefaults | Lets you configure the same concepts, in a similar fashion. |
- @Cacheable / @CacheResult:用于读取 / 设置缓存
- @CachePut:添加 / 更新缓存
- @CacheEvict / @CacheRemove:移除缓存
# 3 issues when using Spring Cache
- 默认 Spring Cache 采用
::
分割数据,并不是约定俗成的冒号分割。 - 默认使用 JDK 序列化,JDK 序列化的问题之前我们也提到了,应该为 JSON 序列化。
- 默认 Spring Cache 注解是不支持 Expire 过期的,但这是日常开发中必然会用到的特性,该如何处理呢?
# 通过自定义CacheManager解决上述问题
@Configuration
public class SpringCacheConfgiration {
@Bean
@Primary //设置默认的CacheManager
public CacheManager cacheManager(LettuceConnectionFactory factory){
//加载默认Spring Cache配置信息
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//设置有效期为1小时
config = config.entryTtl(Duration.ofHours(1));
//说明缓存Key使用单冒号进行分割
config = config.computePrefixWith(cacheName -> cacheName + ":");
//Redis Key采用String直接存储
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
//Redis Value则将对象采用JSON形式存储
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
//不缓存Null值对象
config = config.disableCachingNullValues();
//实例化CacheManger缓存管理器
RedisCacheManager cacheManager = RedisCacheManager.RedisCacheManagerBuilder
//绑定REDIS连接工厂
.fromConnectionFactory(factory)
//绑定配置对象
.cacheDefaults(config)
//与声明式事务注解@Transactional进行兼容
.transactionAware()
//完成对象构建
.build();
return cacheManager;
}
//为不同的TTL创建不同的CacheManager
@Bean
public CacheManager cacheManager1m(LettuceConnectionFactory factory){
//加载默认Spring Cache配置信息
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//设置有效期为1小时
config = config.entryTtl(Duration.ofMinutes(1));
//说明缓存Key使用单冒号进行分割
config = config.computePrefixWith(cacheName -> cacheName + ":");
//Redis Key采用String直接存储
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
//Redis Value则将对象采用JSON形式存储
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
//不缓存Null值对象
config = config.disableCachingNullValues();
//实例化CacheManger缓存管理器
RedisCacheManager cacheManager = RedisCacheManager.RedisCacheManagerBuilder
//绑定REDIS连接工厂
.fromConnectionFactory(factory)
//绑定配置对象
.cacheDefaults(config)
//与声明式事务注解@Transactional进行兼容
.transactionAware()
//完成对象构建
.build();
return cacheManager;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
这里还要注意一个点,这里是责任链的设计模式, 不能断开赋值的
自定义CacheManager
通过对RedisCacheConfiguration
进行一系列配置来解决使用Spring Cache
时遇到的问题:
- 更改数据分割符号:使用
config = config.computePrefixWith(cacheName -> cacheName + ":");
语句,将缓存Key
的分割符号设置为冒号。这样就把默认的::
分割改为了约定俗成的冒号分割,满足开发者的使用习惯。 - 替换序列化方式:通过
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
和config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
这两句,将Redis Key
设置为采用String
直接存储 ,Redis Value
则将对象采用JSON
形式存储,把默认的JDK序列化替换为JSON序列化,解决了JDK序列化存在的问题。 - 支持过期时间设置:在
RedisCacheConfiguration
中使用config = config.entryTtl(Duration.ofHours(1));
(以设置1小时有效期为例)或config = config.entryTtl(Duration.ofMinutes(1));
(设置1分钟有效期)来设置缓存的有效期。在@Cacheable
注解中增加cacheManager
属性,指定使用不同配置的CacheManager
,从而实现不同的过期时间设置,满足日常开发中对缓存过期时间的需求。
在Service使用时,如果需要特别的过期时间,需要在@Cacheable
增加cacheManager
属性
@Service
public class EmpService {
@Resource
EmpDAO empDao;
@Cacheable(value = "emp" , key = "#empId" ,condition = "#empId != 1000", cacheManager = "cacheManager1m")
public Emp findById(Integer empId) {
return empDao.findById(empId);
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
完善页面 (opens new window)