做手机验证码的时候,一般都有五分钟或十分钟的限时机制,所以就需要把“号码–验证码”的信息暂存起来,过期便无效——类似于 Redis 自带过期的机制就适合了。不过应用 Redis 此类缓存模块要专门搭建环境和配置——比较繁琐,于是想到用 JVM 的缓存来做。关键地,我参考了该资源:
https://blog.csdn.net/wab719591157/article/details/78029861
并在这个基础上重写一遍,主要是改造为我自己的编码风格(例如泛型的处理,函数式 Lambda 代替 Interface 等),并且加入缓存自动失效的功能。实际上原作者已经足够简单了,仅仅60代码却清晰明了地说明问题,非常不错。
在安排周期性执行任务的问题上,有网友评论说用 ScheduledExecutorService
实现。我觉得对于这类单个线程的定时器,简单用 Timer + TimerTask
便足够了。如果实在不行敬请大家提出。
源码整合在我的 AJAXJS 框架中:https://gitee.com/sp42_admin/ajaxjs/tree/master/ajaxjs-base/src/main/java/com/ajaxjs/util/cache
存入数据的时候把存入的实际数据增加一个外包装,顺便加上存入时间,和过期时间。
package com.ajaxjs.util.cache;
import java.util.Date;
import java.util.function.Supplier;
/**
*
* 缓存数据。存入数据的时候把存入的实际数据增加一个外包装,顺便加上存入时间,和过期时间
*
* @author sp42 [email protected]
*
* @param 缓存类型,可以是任意类型
*/
public class ExpireCacheData<T> {
/**
* 创建缓存数据
*
* @param t 缓存的数据,可以是任意类型的对象
* @param expire 过期时间,单位是秒
*/
ExpireCacheData(T t, int expire) {
this.data = t;
this.expire = expire <= 0 ? 0 : expire * 1000;
this.saveTime = new Date().getTime() + this.expire;
}
/**
* 创建缓存数据
*
* @param t 缓存的数据,可以是任意类型的对象
* @param expire 过期时间,单位是秒
* @param load 数据装载器
*/
ExpireCacheData(T t, int expire, Supplier<T> load) {
this(t, expire);
this.load = load;
}
/**
* 缓存的数据,可以是任意类型的对象
*/
public T data;
/**
* 过期时间 小于等于0标识永久存活
*/
public long expire;
/**
* 存活时间
*/
public long saveTime;
/**
* 还可以增加一个数据装载器,如果缓存中没有数据或者已经过期,则调用数据装载器加载最新的数据并且加入缓存,并返回。
*/
public Supplier<T> load;
}
我喜欢用继承,于是 ExpireCache 继承了 ConcurrentHashMap,本质上它依然是个 Map,自然有 get/put 方法,也相当于一个非常简单的内存缓存了。而且它本身承托了一个 static 对象 CACHE,这里权且当作单例使用。
package com.ajaxjs.util.cache;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
/**
* 带过期时间的内存缓存。 参考:https://blog.csdn.net/wab719591157/article/details/78029861
*
* @author sp42 [email protected]
*
*/
public class ExpireCache extends ConcurrentHashMap<String, ExpireCacheData<Object>> {
private static final long serialVersionUID = 3850668473354271847L;
/**
* 单例,外界一般调用该对象的方法
*/
public final static ExpireCache CACHE = new ExpireCache();
static {
// 缓存自动失效
// 周期性执行任务,另外可以考虑 ScheduledExecutorService
new Timer().schedule(new TimerTask() {
@Override
public void run() {
for (String key : CACHE.keySet()) {
Object obj = CACHE.get(key);
/*
* 超时了,干掉!另外:有 load 的总是不会返回 null,所以这里不用考虑有 load 的 ExpireCacheData。 而且有 load
* 的也不应该被 kill
*/
if (obj == null)
CACHE.remove(key);
}
}
}, 0, 10 * 1000); // 10 秒钟清理一次
}
/**
* 获取缓存中的数据
*
* @param key 缓存 KEY
* @return 缓存的数据,找不到返回或者过期则直接返回 null
*/
public Object get(String key) {
ExpireCacheData<Object> data = super.get(key);
long now = new Date().getTime();
if (data != null && (data.expire <= 0 || data.saveTime >= now))
return data.data;
else if (data.load != null) {
Object value = data.load.get();
data.data = value;
data.saveTime = now + data.expire; // 重新算过存活时间
return value;
}
return null;
}
/**
* 获取缓存中的数据(避免强类型转换的麻烦)
*
* @param 缓存的类型
* @param key 缓存 KEY
* @param clz 缓存的类型
* @return 缓存的数据,找不到返回或者过期则直接返回 null
*/
@SuppressWarnings({ "unchecked" })
public <T> T get(String key, Class<T> clz) {
Object obj = get(key);
return obj == null ? null : (T) obj;
}
/**
*
* @param key 缓存 KEY
* @param data 要缓存的数据
* @param expire 过期时间
*/
public void put(String key, Object data, int expire) {
put(key, new ExpireCacheData<>(data, expire));
}
/**
*
* @param key 缓存 KEY
* @param data 要缓存的数据
* @param expire 过期时间
* @param load 数据装载器,如果缓存中没有数据或者已经过期,则调用数据装载器加载最新的数据并且加入缓存,并返回
*/
public void put(String key, Object data, int expire, Supplier<Object> load) {
put(key, new ExpireCacheData<>(data, expire, load));
}
}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import com.ajaxjs.util.cache.ExpireCache;
public class TestCache {
@Test
public void testExpire() throws InterruptedException {
ExpireCache.CACHE.put("foo", "bar", 3);
Thread.sleep(1000);
assertEquals("bar", ExpireCache.CACHE.get("foo"));
Thread.sleep(2010);
assertNull(ExpireCache.CACHE.get("foo"));
ExpireCache.CACHE.put("bar", "foo", 2, () -> "foo-2");
Thread.sleep(1000);
assertEquals("foo", ExpireCache.CACHE.get("bar"));
Thread.sleep(2010);
assertEquals("foo-2", ExpireCache.CACHE.get("bar"));
}
}