参考链接
Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,ConcurrentMapCache等;
- 每次调用需要缓存功能的方法时,
Spring会检查检查指定参数的指定的目标方法是否已经被调用过
;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。 - 使用Spring缓存抽象时我们需要关注以下两点;
- 确定方法需要被缓存以及他们的缓存策略
- 从缓存中读取之前缓存存储的数据
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
如果不用的第三方的话,默认情况使用 ConcurrentMap缓存
重点知识点
名称 | 解释 |
---|---|
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager | 缓存管理器,管理各种缓存(cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 与@Cacheable区别在于是否每次都调用方法,常用于更新 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
@CacheConfig | 统一配置本类的缓存注解的属性 |
@Cacheable/@CachePut/@CacheEvict 主要的参数
名称 | 解释 |
---|---|
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写, 如果不指定,则缺省按照方法的所有参数进行组合 例如: @Cacheable(value=”testcache”,key=”#id”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false, 只有为 true 才进行缓存/清除缓存 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
unless | 否定缓存。当条件结果为TRUE时,就不会缓存。 @Cacheable(value=”testcache”,unless=”#userName.length()>2”) |
allEntries (@CacheEvict ) | 是否清空所有缓存内容,缺省为 false,如果指定为 true, 则方法调用后将立即清空所有缓存 例如: @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation (@CacheEvict) | 是否在方法执行前就清空,缺省为 false,如果指定为 true, 则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法 执行抛出异常,则不会清空缓存 例如: @CachEvict(value=”testcache”,beforeInvocation=true) |
-
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
名称 位置 描述 示例 methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result
注意:
1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如
@Cacheable(key = "targetClass + methodName +#p0")
2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:
@Cacheable(value="users", key="#id")
@Cacheable(value="users", key="#p0")
SpEL提供了多种运算符
类型 | 运算符 |
---|---|
关系 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
算术 | +,- ,* ,/,%,^ |
逻辑 | &&,||,!,and,or,not,between,instanceof |
条件 | ?: (ternary),?: (elvis) |
正则表达式 | matches |
其他类型 | ?.,?[…],![…],$[…] |
原理
@Cacheable
- 方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取,CacheManage获取相应的缓存,第一次获取缓存如果没有cache组件会自动创建
- 去cache中查找缓存的内容,使用一个key 默认方法的参数,key是按照某种策略生成的,默认是KeyGenerator生成的,默认是使用SimpleKeyGenerator
- 没有查到缓存叫调用目标方法
- 将目标方法的结果,放进缓存
整合Redis
Redis 优势
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性
整合
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
当你导入这一个依赖时,
SpringBoot
的CacheManager
就会使用RedisCache
。如果你的Redis使用默认配置,这时候已经可以启动程序了。
# Redis数据库索引(默认为0)
spring.redis.database=1
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=1000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=2
# 连接超时时间(毫秒)
spring.redis.timeout=
原理
springBoot加载缓存组件
- CacheManager === Cache缓存组件来实际给缓存中存取数据
- 引入redis的starter,容器中保存的是RedisCacheManager
@Configuration
@ConditionalOnClass({RedisConnectionFactory.class})
@AutoConfigureAfter({RedisAutoConfiguration.class})
@ConditionalOnBean({RedisConnectionFactory.class})
@ConditionalOnMissingBean({CacheManager.class}) //
@Conditional({CacheCondition.class})
class RedisCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
private final org.springframework.data.redis.cache.RedisCacheConfiguration redisCacheConfiguration;
- RedisCacheManager 帮我们创建RedisCache来作为缓存组件,RedisCache通过操作缓存数据的
- 默认保存数据是 k-v都是Object;利用序列化保存的
自定义序列化方式
- 如何保存为JSON?
修改RedisTemplate序列化方式
Spring boot集成 Redis 客户端jedis(
不同版本spring-boot-starter-data-redis使用的策略不一样,高版本也有用lettuce的
)。此段代码可copy
RedisAutoConfiguration.class
文件下的代码片段,然后自己添加序列化代码即可
@Bean
public RedisTemplate<Object, Stu> stuRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Stu> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Stu>(Stu.class));
return template;
}
@Autowired
StuService stuService;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
RedisTemplate<Object,Stu> stuRedisTemplate;
@Autowired
RedisTemplate redisTemplate;
/**
* redis操作
*
* 1,string
* 2,list
* 3,set
* 4,hash
* 5,zset
*/
@Test
public void test01(){
stringRedisTemplate.opsForValue().set("string","string");
stringRedisTemplate.opsForList().leftPush("list","1");
stringRedisTemplate.opsForSet().add("set","dsadsa");
stringRedisTemplate.opsForSet().add("set","dsadsa","2321","ewq");
}
@Test
public void test02(){
//默认如果保存对象,使用jdk序列化机制,序列化后的数据存在redis里
redisTemplate.opsForValue().set("ob", new Stu(1, "hcy", 20, null, null));
//1、将数据以json的方式保存
// 01、自己将对象转为json
// 02、redisTemplate默认序列化规则
Object ob = redisTemplate.opsForValue().get("ob");
Stu stu = (Stu) ob;
System.out.println(stu.getStuName());
}
@Test
public void test03(){
//默认如果保存对象,使用jdk序列化机制,序列化后的数据存在redis里
stuRedisTemplate.opsForValue().set("objjjj", new Stu(1, "hcy", 20, null, null));
//1、将数据以json的方式保存
// 01、自己将对象转为json
// 02、redisTemplate默认序列化规则 修改默认规则
Object ob = redisTemplate.opsForValue().get("ob");
Stu stu = (Stu) ob;
System.out.println(stu.getStuName());
}
自定义RedisCacheManage
这里说一下博主的一个想法,
RedisCacheManager
之前的版本应该是有一个构造方法直接传入RedisTemplate
的,只要重写一下RedisCacheConfiguration
里的bean就行;我使用的这个版本是不可以的,需要传入
RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation
,后来也没那么做我的版本是
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
.disableCachingNullValues();
RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
return redisCacheManager;
}
private RedisSerializer<String> keySerializer() {
return new StringRedisSerializer();
}
private RedisSerializer<Object> valueSerializer() {
return new GenericJackson2JsonRedisSerializer();
}