一、导出依赖
<!--caffeine-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- 开启springboot 注解式使用缓存功能-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
二、缓存配置
本人是配置到springboot的启动文件里了,自己可以根据需求自由配置
需要在启动文件里加入@EnableCaching
注解,开启注解支持
@Bean
@Primary
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<CaffeineCache> caches = new ArrayList<>();
for (Caches c : Caches.values()) {
caches.add(new CaffeineCache(c.name(),
Caffeine.newBuilder()
.recordStats()
.expireAfterWrite(c.getTtl(), TimeUnit.SECONDS)
.maximumSize(c.getMaxSize())
.softValues()
//.weakKeys() 弱引用生成key,容易造成缓存失效
//.weakValues() 弱引用生成value,容易造成缓存失效
.build())
);
}
cacheManager.setCaches(caches);
return cacheManager;
}
/**
* 缓存定义
*/
public enum Caches {
plugin (1200, 30), // 20分钟
userToken (1200, 100), //用户 userToken的缓存配置
customer (1200,50),
CACHE_3MINUTES(180,50);
private int ttl;
private int maxSize;
Caches(int ttl, int maxSize) {
this.ttl = ttl;
this.maxSize = maxSize;
}
public int getTtl() {
return ttl;
}
public int getMaxSize() {
return maxSize;
}
}
缓存名称配置
public interface CacheNameTimeConstant {
String CACHE_DEFAULT = "CACHE_DEFAULT" ;
String CACHE_3MINUTES = "CACHE_3MINUTES" ;
String plugin = "plugin";
String userToken = "userToken";
String customer = "customer";
}
三、使用缓存
@Cacheable(value = CacheNameTimeConstant.userToken,key = "#root.methodName.#userId",unless = "#result==null")
public UserTokenPO queryUserByUserId(String userId) {
log.info("【UserService.queryUserByUserId】未走缓存!userId为 -> {}",userId);
UserTokenPO userPO = userTokenRepository.getOne(new LambdaQueryWrapper<UserTokenPO>().eq(UserTokenPO::getUserId, userId));
if (ObjUtil.isNotEmpty(userPO)
&& StrUtil.isNotBlank(userPO.getOpenId())
&& StrUtil.isNotBlank(userPO.getUserKey())) {
log.info("从db中获取User成功, openId={} calendarId = {} ", userPO.getUserId(), userPO.getCalendarId());
return userPO;
}
}
四、概念解释
一些概念
名称 | 解释 |
---|---|
@Cacheable | 主要针对方法配置,标注此方法使用缓存(环绕通知) |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用后,更新缓存(后置通知) |
@EnableCaching | 在启动文件中,开启基于注解的缓存 |
@CacheConfig | 统一配置本类的缓存注解的属性 |
cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache、caffeine等 |
CacheManager | 缓存管理器,管理各种缓存(cache)组件 |
keyGenerator | 缓存数据时key生成策略 |
@Cacheable/@CachePut/@CacheEvict 的参数
名称 | 解释 |
---|---|
value、cacheNames | 二者选其一即可。缓存的名称,在 spring 配置中定义,必须指定至少一个。例如:@Cacheable(value=”cacheName”) 或者@Cacheable(value={”cacheName1”,”cacheName2”} |
key | 缓存的 key,spring 官方更推荐显式指定 key 的方式,即指定 @Cacheable 的 key 参数例如:@Cacheable(key=”#id”) |
keyGenerator | 当我们在声明 @Cacheable 时不指定 key 参数,则该缓存名下的所有 key 会使用 KeyGenerator 根据参数 自动生成。spring 有一个默认的 SimpleKeyGenerator ,在 spring boot 自动化配置中,这个会被默认注入。 |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存.例如:@Cacheable(value=”userToken”,condition=”#age>2”) |
unless | 否定缓存。当条件结果为TRUE时,就不会缓存。@Cacheable(value=”userToken”,unless=”#”#age>2”) |
allEntries(@CacheEvict注解参数) | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存.例如:@CachEvict(value=”userToken”,allEntries=true) |
beforeInvocation(@CacheEvict注解参数) | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法,执行抛出异常,则不会清空缓存.例如:@CachEvict(value=”userToken”,beforeInvocation=true) |
SpEL表达式数据
当我们要使用root对象的属性作为key时我们也可以将“#root”省略
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
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 |
SpEL提供的运算符
类型 | 运算符 |
---|---|
关系 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
算术 | +,- ,* ,/,%,^ |
逻辑 | &&, |
条件 | ?: (ternary),?: (elvis) |
正则表达式 | matches |
其他类型 | ?.,?[…],![…],1,$[…] |
五、缓存失效的原因
弱引用
:在配置CacheManager时,指定了key和vaule为弱引用,导致缓存失效
弱引用与软引用的区别:
只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。所以被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。
注:
虽说优先级很低,但是我在本地跑项目时,每次都不走缓存,改成软引用后正常!
原因:
缓存管理器使用了weakKeys()的配置,这就导致了一个问问题,经过代理之后,每次key都匹配不上,因为底层使用的==的方式来进行key的匹配,而不是equals,所以,需要移除该配置。
2.未加@EnableCaching
注解
3.一个方法A调同一个类里的另一个有缓存注解的方法B,这样是不走缓存的。
因为@Cacheable 是使用AOP 代理实现的 ,通过创建内部类来代理缓存方法,这样就会导致一个问题,类内部的方法调用类内部的缓存方法不会走代理,不会走代理,就不能正常创建缓存,所以每次都需要去调用数据库。