无法使用redis或其他中间件时,Java本地缓存实现
Posted Ps_Q
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了无法使用redis或其他中间件时,Java本地缓存实现相关的知识,希望对你有一定的参考价值。
无法使用redis或其他中间件时,Java本地缓存实现
在项目遇到一个问题,需要保存一些验证码,但由于是老项目,并且没有基于maven,因此导包比较麻烦。好在项目基于spring,而spring中的org.springframework.cache.Cache相关类恰好可以解决。真是不幸中的万幸。
创建了三个类,一个缓存工具类,一个管理缓存工具类,一个过期时间处理类。
当时并不具备导入外部工具的条件,只有基于spring4.0+本本,连springboot都不具备,当然这是在我本地代码重现当时的情况,增加了Ali JSON处理工具,hutool工具pom如下。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</dependency>
LocalCache 类的内容
public class LocalCache
/**
* 当前自动清理任务状态,true为暂未启动,false为已经启动
*/
private static volatile AtomicBoolean state = new AtomicBoolean(true);
/**
* 实时缓存名
*/
private static volatile Set<String> cacheNames = Collections.synchronizedSet(new HashSet<>());
/**
* @param cacheName 缓存名
* @param key key
* @param value value
* @param milliseconds 过期时间
*/
public static void put(String cacheName, String key, Object value, Long milliseconds)
Cache cache = LocalCacheManager.getCache(cacheName);
synchronized (cache)
String valueJson = JSON.toJSONString(value);
valueJson = CacheTime.getEncodeValue(valueJson, milliseconds);
cache.put(key, valueJson);
cacheNames.add(cacheName);
if (state.get())
autoClearExpiredCache();
public static <T> T get(String cacheName, String key, Class<T> t)
Cache cache = LocalCacheManager.getCache(cacheName);
String encodeValue = cache.get(key, String.class);
String value = CacheTime.decodeValue(encodeValue);
if (StrUtil.isBlank(value))
cache.evict(key);
return null;
return JSON.parseObject(value, t);
public void remove(String cacheName, String key)
Cache cache = LocalCacheManager.getCache(cacheName);
synchronized (cache)
cache.evict(key);
private static void autoClearExpiredCache()
new Thread(() ->
while (cacheNames.size() > 0)
state.set(false);
try
Thread.sleep(30000L);
catch (InterruptedException e)
e.printStackTrace();
Set<String> set = LocalCache.cacheNames;
set.forEach(cacheName ->
Cache cache = LocalCacheManager.getCache(cacheName);
if (Objects.nonNull(cache))
synchronized (cache)
ConcurrentHashMap store = (ConcurrentHashMap) cache.getNativeCache();
if (CollUtil.isEmpty(store))
cacheNames.remove(cacheName);
else
store.forEach((key, value) ->
String decodeValue = CacheTime.decodeValue((String) value);
if (StrUtil.isBlank(decodeValue))
cache.evict(key);
);
else
cacheNames.remove(cacheName);
);
// 释放空间 初始化任务状态
LocalCacheManager.clear();
state.set(true);
).start();
CacheTime
public class CacheTime
/**
* 编码分隔符
*/
private static char delimiter = '|';
/**
* 获取编码后的value 用于控制缓存过期时间
* @param value 缓存的value
* @param time 缓存有效时长
* @return
*/
public static String getEncodeValue(String value, Long time)
Long now = System.currentTimeMillis();
Long expireDate = now + time;
return new StringBuilder()
.append(expireDate)
.append(delimiter)
.append(value)
.toString();
/**
* 解码Value并判断是否过期
* @param encodeValue 缓存编码后的value
* @return
*/
public static String decodeValue(String encodeValue)
if(Objects.isNull(encodeValue))
return null;
int index = encodeValue.indexOf(delimiter);
String timeStamp = encodeValue.substring(0, index);
if(Long.parseLong(timeStamp) > System.currentTimeMillis())
return encodeValue.substring(index + 1);
return null;
LocalCacheManager
public class LocalCacheManager
private volatile static ConcurrentMapCacheManager cacheManager = null;
private static ConcurrentMapCacheManager cacheManager()
if(null == cacheManager)
synchronized (LocalCacheManager.class)
if(null == cacheManager)
cacheManager = new ConcurrentMapCacheManager();
return cacheManager;
public static Cache getCache(String cacheName)
return cacheManager().getCache(cacheName);
public static void clear()
cacheManager = null;
基本上就是实现了一个类似于redis的工具吧。借鉴了老哥的代码。改成了一个单独线程自动清理过期缓存,由于我使用这段代码的时候只在特殊场景,大部分情况其实缓存都是空的,所有自动清理任务在缓存为空的情况是不运行的,包括缓存工具类的静态变量也是null。节约了一些资源。
https://blog.csdn.net/qq_38634643/article/details/118785050
但在不考虑缓存过期的情况,也可以不需要整这么些。
直接使用HashMap即可解决问题
public class LRUCache extends LinkedHashMap
/**
* 可重入读写锁,保证并发读写安全性
*/
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock readLock = readWriteLock.readLock();
private Lock writeLock = readWriteLock.writeLock();
/**
* 缓存大小限制
*/
private int maxSize;
public LRUCache(int maxSize)
super(maxSize + 1, 1.0f, true);
this.maxSize = maxSize;
@Override
public Object get(Object key)
readLock.lock();
try
return super.get(key);
finally
readLock.unlock();
@Override
public Object put(Object key, Object value)
writeLock.lock();
try
return super.put(key, value);
finally
writeLock.unlock();
@Override
protected boolean removeEldestEntry(Map.Entry eldest)
return this.size() > maxSize;
当然也有其他非常方便的工具类
google.guava
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
public class GuavaCacheMain
public static void main(String[] args) throws Exception
//创建guava cache
Cache<String, String> loadingCache = CacheBuilder.newBuilder()
//cache的初始容量
.initialCapacity(5)
//cache最大缓存数
.maximumSize(10)
//设置写缓存后n秒钟过期
.expireAfterWrite(17, TimeUnit.SECONDS)
//设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
//.expireAfterAccess(17, TimeUnit.SECONDS)
.build();
String key = "key";
// 往缓存写数据
loadingCache.put(key, "v");
// 获取value的值,如果key不存在,调用collable方法获取value值加载到key中再返回
String value = loadingCache.get(key, new Callable<String>()
@Override
public String call() throws Exception
return getValueFromDB(key);
);
// 删除key
loadingCache.invalidate(key);
private static String getValueFromDB(String key)
return "v";
Caffeine (强力推荐) 在spring 5.0之后是默认的,是基于Guava在jdk1.8后重写的,性能最佳
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.0</version>
</dependency>
或者
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
public class CaffeineCacheTest
public static void main(String[] args) throws Exception
//创建guava cache
Cache<String, String> loadingCache = Caffeine.newBuilder()
//cache的初始容量
.initialCapacity(5)
//cache最大缓存数
.maximumSize(10)
//设置写缓存后n秒钟过期
.expireAfterWrite(17, TimeUnit.SECONDS)
//设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
//.expireAfterAccess(17, TimeUnit.SECONDS)
.build();
String key = "key";
// 往缓存写数据
loadingCache.put(key, "v");
// 获取value的值,如果key不存在,获取value后再返回
String value = loadingCache.get(key, CaffeineCacheTest::getValueFromDB);
// 删除key
loadingCache.invalidate(key);
private static String getValueFromDB(String key)
return "v";
Encache 是Hibernate默认的
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.0</version>
</dependency>
public class EncacheMain
public static void main(String[] args) throws Exception
// 声明一个cacheBuilder
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("encacheInstance", CacheConfigurationBuilder
//声明一个容量为20的堆内缓存
.newCacheConfigurationBuilder(String.class,String.class, ResourcePoolsBuilder.heap(20)))
.build(true);
// 获取Cache实例
Cache<String,String> myCache = cacheManager.getCache("encacheInstance", String.class, String.class);
// 写缓存
myCache.put("key","v");
// 读缓存
String value = myCache.get("key");
// 移除换粗
cacheManager.removeCache("myCache");
cacheManager.close();
以上是关于无法使用redis或其他中间件时,Java本地缓存实现的主要内容,如果未能解决你的问题,请参考以下文章