简简单单做一个带过期时间的内存缓存
做手机验证码的时候,一般都有五分钟或十分钟的限时机制,所以就需要把“号码–验证码”的信息暂存起来,过期便无效——类似于 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
ExpireCacheData
存入数据的时候把存入的实际数据增加一个外包装,顺便加上存入时间,和过期时间。
package com.ajaxjs.util.cache;
import java.util.Date;
import java.util.function.Supplier;
/** * * 缓存数据。存入数据的时候把存入的实际数据增加一个外包装,顺便加上存入时间,和过期时间 * * @author sp42 frank@ajaxjs.com * * @param <T> 缓存类型,可以是任意类型 */
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 管理器
我喜欢用继承,于是 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 frank@ajaxjs.com * */
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 <T> 缓存的类型 * @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"));
}
}
还没有评论,来说两句吧...