3. SpringBoot 中默认Cache-Caffine Cache
SpringBoot 1.x版本中的默认本地cache是Guava Cache。在2.x(Spring Boot 2.0(spring 5) )版本中已经用Caffine Cache取代了Guava Cache。毕竟有了更优的缓存淘汰策略。
下面我们来说在SpringBoot2.x版本中如何使用cache。
1. 引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.6.2</version> </dependency>
2. 添加注解开启缓存支持
添加@EnableCaching注解:
@SpringBootApplication @EnableCaching public class SingleDatabaseApplication { public static void main(String[] args) { SpringApplication.run(SingleDatabaseApplication.class, args); } }
3. 配置文件的方式注入相关参数
properties文件
spring.cache.cache-names=cache1 spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=10s
或Yaml文件
spring: cache: type: caffeine cache-names: - userCache caffeine: spec: maximumSize=1024,refreshAfterWrite=60s
如果使用refreshAfterWrite配置,必须指定一个CacheLoader.不用该配置则无需这个bean,如上所述,该CacheLoader将关联被该缓存管理器管理的所有缓存,所以必须定义为CacheLoader<Object, Object>,自动配置将忽略所有泛型类型。
import com.github.benmanes.caffeine.cache.CacheLoader; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author: rickiyang * @date: 2019/6/15 * @description: */ @Configuration public class CacheConfig { /** * 相当于在构建LoadingCache对象的时候 build()方法中指定过期之后的加载策略方法 * 必须要指定这个Bean,refreshAfterWrite=60s属性才生效 * @return */ @Bean public CacheLoader<String, Object> cacheLoader() { CacheLoader<String, Object> cacheLoader = new CacheLoader<String, Object>() { @Override public Object load(String key) throws Exception { return null; } // 重写这个方法将oldValue值返回回去,进而刷新缓存 @Override public Object reload(String key, Object oldValue) throws Exception { return oldValue; } }; return cacheLoader; } }
Caffeine常用配置说明:
initialCapacity=[integer]: 初始的缓存空间大小 maximumSize=[long]: 缓存的最大条数 maximumWeight=[long]: 缓存的最大权重 expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期 expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期 refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存 weakKeys: 打开key的弱引用 weakValues:打开value的弱引用 softValues:打开value的软引用 recordStats:开发统计功能 注意: expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。 maximumSize和maximumWeight不可以同时使用 weakValues和softValues不可以同时使用
需要说明的是,使用配置文件的方式来进行缓存项配置,一般情况能满足使用需求,但是灵活性不是很高,如果我们有很多缓存项的情况下写起来会导致配置文件很长。所以一般情况下你也可以选择使用bean的方式来初始化Cache实例。
下面的演示使用bean的方式来注入:
package com.rickiyang.learn.cache; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import org.apache.commons.compress.utils.Lists; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCache; import org.springframework.cache.support.SimpleCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * @author: rickiyang * @date: 2019/6/15 * @description: */ @Configuration public class CacheConfig { /** * 创建基于Caffeine的Cache Manager * 初始化一些key存入 * @return */ @Bean @Primary public CacheManager caffeineCacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); ArrayList<CaffeineCache> caches = Lists.newArrayList(); List<CacheBean> list = setCacheBean(); for(CacheBean cacheBean : list){ caches.add(new CaffeineCache(cacheBean.getKey(), Caffeine.newBuilder().recordStats() .expireAfterWrite(cacheBean.getTtl(), TimeUnit.SECONDS) .maximumSize(cacheBean.getMaximumSize()) .build())); } cacheManager.setCaches(caches); return cacheManager; } /** * 初始化一些缓存的 key * @return */ private List<CacheBean> setCacheBean(){ List<CacheBean> list = Lists.newArrayList(); CacheBean userCache = new CacheBean(); userCache.setKey("userCache"); userCache.setTtl(60); userCache.setMaximumSize(10000); CacheBean deptCache = new CacheBean(); deptCache.setKey("userCache"); deptCache.setTtl(60); deptCache.setMaximumSize(10000); list.add(userCache); list.add(deptCache); return list; } class CacheBean { private String key; private long ttl; private long maximumSize; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public long getTtl() { return ttl; } public void setTtl(long ttl) { this.ttl = ttl; } public long getMaximumSize() { return maximumSize; } public void setMaximumSize(long maximumSize) { this.maximumSize = maximumSize; } } }
创建了一个SimpleCacheManager
作为Cache的管理对象,然后初始化了两个Cache对象,分别存储user,dept类型的缓存。当然构建Cache的参数设置我写的比较简单,你在使用的时候酌情根据需要配置参数。
4. 使用注解来对 cache 增删改查
我们可以使用spring提供的 @Cacheable
、@CachePut
、@CacheEvict
等注解来方便的使用caffeine缓存。
如果使用了多个cahce,比如redis、caffeine等,必须指定某一个CacheManage为@primary,在@Cacheable注解中没指定 cacheManager 则使用标记为primary的那个。
cache方面的注解主要有以下5个:
- @Cacheable 触发缓存入口(这里一般放在创建和获取的方法上,
@Cacheable
注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存) - @CacheEvict 触发缓存的eviction(用于删除的方法上)
- @CachePut 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)
- @Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
- @CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)
说一下@Cacheable
和 @CachePut
的区别:
@Cacheable:它的注解的方法是否被执行取决于Cacheable中的条件,方法很多时候都可能不被执行。
@CachePut:这个注解不会影响方法的执行,也就是说无论它配置的条件是什么,方法都会被执行,更多的时候是被用到修改上。
简要说一下Cacheable类中各个方法的使用:
public @interface Cacheable { /** * 要使用的cache的名字 */ @AliasFor("cacheNames") String[] value() default {}; /** * 同value(),决定要使用那个/些缓存 */ @AliasFor("value") String[] cacheNames() default {}; /** * 使用SpEL表达式来设定缓存的key,如果不设置默认方法上所有参数都会作为key的一部分 */ String key() default ""; /** * 用来生成key,与key()不可以共用 */ String keyGenerator() default ""; /** * 设定要使用的cacheManager,必须先设置好cacheManager的bean,这是使用该bean的名字 */ String cacheManager() default ""; /** * 使用cacheResolver来设定使用的缓存,用法同cacheManager,但是与cacheManager不可以同时使用 */ String cacheResolver() default ""; /** * 使用SpEL表达式设定出发缓存的条件,在方法执行前生效 */ String condition() default ""; /** * 使用SpEL设置出发缓存的条件,这里是方法执行完生效,所以条件中可以有方法执行后的value */ String unless() default ""; /** * 用于同步的,在缓存失效(过期不存在等各种原因)的时候,如果多个线程同时访问被标注的方法 * 则只允许一个线程通过去执行方法 */ boolean sync() default false; }
基于注解的使用方法:
package com.rickiyang.learn.cache; import com.rickiyang.learn.entity.User; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; /** * @author: rickiyang * @date: 2019/6/15 * @description: 本地cache */ @Service public class UserCacheService { /** * 查找 * 先查缓存,如果查不到,会查数据库并存入缓存 * @param id */ @Cacheable(value = "userCache", key = "#id", sync = true) public void getUser(long id){ //查找数据库 } /** * 更新/保存 * @param user */ @CachePut(value = "userCache", key = "#user.id") public void saveUser(User user){ //todo 保存数据库 } /** * 删除 * @param user */ @CacheEvict(value = "userCache",key = "#user.id") public void delUser(User user){ //todo 保存数据库 } }
如果你不想使用注解的方式去操作缓存,也可以直接使用SimpleCacheManager获取缓存的key进而进行操作。
注意到上面的key使用了spEL 表达式。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="userCache", key="#id") @Cacheable(value="userCache", key="#p0")
SpEL提供了多种运算符
类型 | 运算符 |
关系 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
算术 | +,- ,* ,/,%,^ |
逻辑 | &&,||,!,and,or,not,between,instanceof |
条件 | ?: (ternary),?: (elvis) |
正则表达式 | matches |
其他类型 | ?.,?[…],![…],^[…],$[…] |