无法使用redis或其他中间件时,Java本地缓存实现
在项目遇到一个问题,需要保存一些验证码,但由于是老项目,并且没有基于maven,因此导包比较麻烦。好在项目基于spring,而spring中的org.springframework.cache.Cache相关类恰好可以解决。真是不幸中的万幸。
·
无法使用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();
}
}
更多推荐
已为社区贡献1条内容
所有评论(0)