项目中使用到缓存的地方有很多,最常见的如用户信息的缓存,用户登录后对token、权限、菜单等信息的缓存,不必每次请求数据库造成请求缓慢且增加数据库压力。那么,常用缓存用那些呢?在这里提供两种,一是Map,另一个是Redis,使用方式不再赘述,后面会附上两种缓存的基本使用工具类。今天主要解决一个问题就是:如果我的项目里想做到==原本使用Map作为缓存工具,但因为Map在项目重启后会丢失数据想切换成Redis做为缓存工具,或者说我默认使用Map做为缓存工具,但要在我配置了Redis信息后自动使用Redis做为缓存工具该如何实现呢?
这里提供一种思路:
定义缓存工具类接口,两个缓存工具类(MapCacheUtil和RedisCacheUtil)分别实现缓存工具类接口,利用Springboot的注解@ConditionalOnExpression和@ConditionalOnMissingBean来根据配置自动决定使用Map或Redis做为缓存工具。
具体实现如下:
- 缓存工具类接口:
public interface CacheUtil {
Long MAX_VALUE = (long) Integer.MAX_VALUE;
TimeUnit DEFAULT_UNIT = TimeUnit.DAYS;
/**
* 设置缓存
*
* @param key 键
* @param value 值
* @return 是否成功
*/
boolean set(String key, Object value);
/**
* 设置带过期时间缓存
*
* @param key 键
* @param value 值
* @param timeout 时间
* @param timeUnit 时间单位
* @return 是否成功
*/
boolean set(String key, Object value, Long timeout, TimeUnit timeUnit);
/**
* 获取一个缓存
*
* @param key 键
* @return 缓存数据
*/
<T >T get(String key);
/**
* 删除一个缓存
*
* @param key 键
* @return 是否删除成功
*/
boolean remove(String key);
/**
* 判断是否存在缓存
*
* @param key 键
* @return 是否存在
*/
boolean hasKey(String key);
}
这里定义缓存工具类的基本功能清单
- Map缓存工具类
@Component
@Slf4j
@ConditionalOnMissingBean(value = RedisCacheUtil.class)
public class MapCacheUtil implements CacheUtil{
private static final Map<String, CacheData> map = new ConcurrentHashMap<>();
// 定期清除过期的key,key的过期时间最大有5S误差
static {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
long currentTimeMillis = System.currentTimeMillis();
for (String key : map.keySet()) {
CacheData cacheData = map.get(key);
if (cacheData == null || currentTimeMillis - cacheData.getLastVisitTime() < (cacheData.getTimeUnit().toSeconds(cacheData.getSeconds()) * 1000)) {
continue;
}
map.remove(key);
log.info("缓存过期,清理缓存:{}", key);
}
}
}, 0, 5000);
}
/**
* 设置缓存
*
* @param key 键
* @param value 值
* @return 是否成功
*/
@Override
public boolean set(String key, Object value) {
return set(key, value, MAX_VALUE, DEFAULT_UNIT);
}
/**
* 设置带过期时间缓存
*
* @param key 键
* @param value 值
* @param timeout 时间
* @param timeUnit 时间单位
* @return 是否成功
*/
@Override
public boolean set(String key, Object value, Long timeout, TimeUnit timeUnit) {
if (StringUtils.isBlank(key)) {
return false;
}
if (timeout == 0) return true;
if (timeUnit == null) timeUnit = TimeUnit.SECONDS;
CacheData cacheData = new CacheData(timeout, timeUnit, value);
map.put(key, cacheData);
return true;
}
/**
* 获取一个缓存
*
* @param key 键
* @return 缓存数据
*/
@Override
@SuppressWarnings("unchecked")
public <T>T get(String key) {
if (StringUtils.isBlank(key)) return null;
CacheData cacheData = map.get(key);
if (cacheData == null) return null;
return (T)cacheData.getData();
}
/**
* 删除一个缓存
*
* @param key 键
* @return 是否删除成功
*/
@Override
public boolean remove(String key) {
if (StringUtils.isBlank(key)) return false;
if (map.get(key) != null) {
map.remove(key);
return true;
}
return false;
}
/**
* 判断是否存在缓存
*
* @param key 键
* @return 是否存在
*/
@Override
public boolean hasKey(String key) {
return map.get(key) != null;
}
private static class CacheData {
/**
* 缓存时长 秒
*/
private Long seconds;
private TimeUnit timeUnit;
private Long lastVisitTime;
private Object data;
public CacheData(long seconds, TimeUnit timeUnit, Object data) {
this.data = data;
this.seconds = seconds;
this.timeUnit = timeUnit;
this.lastVisitTime = System.currentTimeMillis();
}
public Long getSeconds() {
return seconds;
}
public void setSeconds(Long seconds) {
this.seconds = seconds;
}
public Long getLastVisitTime() {
return lastVisitTime;
}
public void setLastVisitTime(Long lastVisitTime) {
this.lastVisitTime = lastVisitTime;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public TimeUnit getTimeUnit() {
return timeUnit;
}
public void setTimeUnit(TimeUnit timeUnit) {
this.timeUnit = timeUnit;
}
}
}
说明:
- @ConditionalOnMissingBean(value = RedisCacheUtil.class) 如果环境中没有RedisCacheUtil这个bean,当前类才会被Springboot加载
- Redis缓存工具类
@Component
@RequiredArgsConstructor
@ConditionalOnExpression("#{environment['spring.redis.host']!=null or environment['spring.redis.cluster.nodes']!=null}")
public class RedisCacheUtil implements CacheUtil {
private final RedisTemplate<String, Object> redisTemplate;
/**
* 设置缓存
*
* @param key 键
* @param value 值
* @return 是否成功
*/
@Override
public boolean set(String key, Object value) {
if (StringUtils.isBlank(key) || value == null) return false;
redisTemplate.opsForValue().set(key, value);
return true;
}
/**
* 设置带过期时间缓存
*
* @param key 键
* @param value 值
* @param timeout 时间
* @param timeUnit 时间单位
* @return 是否成功
*/
@Override
public boolean set(String key, Object value, Long timeout, TimeUnit timeUnit) {
if (StringUtils.isBlank(key) || value == null) return false;
if (timeUnit == null) timeUnit = TimeUnit.SECONDS;
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
return true;
}
/**
* 获取一个缓存
*
* @param key 键
* @return 缓存数据
*/
@Override
@SuppressWarnings("unchecked")
public <T> T get(String key) {
if (StringUtils.isBlank(key)) return null;
Object o = redisTemplate.opsForValue().get(key);
if (o == null) return null;
return (T) o;
}
/**
* 删除一个缓存
*
* @param key 键
* @return 是否删除成功
*/
@Override
@SuppressWarnings("all")
public boolean remove(String key) {
if (StringUtils.isBlank(key)) return false;
return redisTemplate.delete(key);
}
/**
* 判断是否存在缓存
*
* @param key 键
* @return 是否存在
*/
@Override
@SuppressWarnings("all")
public boolean hasKey(String key) {
if (StringUtils.isBlank(key)) return false;
return redisTemplate.hasKey(key);
}
}
说明:
- @ConditionalOnExpression("#{environment['spring.redis.host']!=null or environment['spring.redis.cluster.nodes']!=null}") 当在配置文件中配置spring.redis.host或spring.redis.cluster.nodes时,当前类会被加载为bean
通过@ConditionalOnExpression和@ConditionalOnMissingBean这两个注解,实现了在未配置redis连接信息时就是用Map做为缓存工具,配置了redis连接信息后就是用Redis做为缓存工具
@ConditionalOnExpression为什么判断了spring.redis.host和spring.redis.cluster.nodes呢?
spring.redis.host用于单机redis的连接,spring.redis.cluster.nodes用于集群的连接,这样配置可支持redis集群
使用:
@RestController
@RequiredArgsConstructor
@Slf4j
public class TestCacheController {
private final CacheUtil cacheUtil;
@GetMapping("/testCache")
public String testCache(){
cacheUtil.set(UUID.randomUUID().toString(),"测试");
cacheUtil.set(UUID.randomUUID().toString(),123);
log.info("设置过期key...");
cacheUtil.set("cc",123456, 10L,null);
Integer cc=cacheUtil.get("cc");
return "success";
}
}
注意private final CacheUtil cacheUtil;的写法,用的是接口而不是具体的实现的构造器注入,也可使用@Autowire或@Resource自动注入