简简单单做一个带过期时间的内存缓存

水深无声 2023-02-12 06:20 16阅读 0赞

做手机验证码的时候,一般都有五分钟或十分钟的限时机制,所以就需要把“号码–验证码”的信息暂存起来,过期便无效——类似于 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

存入数据的时候把存入的实际数据增加一个外包装,顺便加上存入时间,和过期时间。

  1. package com.ajaxjs.util.cache;
  2. import java.util.Date;
  3. import java.util.function.Supplier;
  4. /** * * 缓存数据。存入数据的时候把存入的实际数据增加一个外包装,顺便加上存入时间,和过期时间 * * @author sp42 frank@ajaxjs.com * * @param <T> 缓存类型,可以是任意类型 */
  5. public class ExpireCacheData<T> {
  6. /** * 创建缓存数据 * * @param t 缓存的数据,可以是任意类型的对象 * @param expire 过期时间,单位是秒 */
  7. ExpireCacheData(T t, int expire) {
  8. this.data = t;
  9. this.expire = expire <= 0 ? 0 : expire * 1000;
  10. this.saveTime = new Date().getTime() + this.expire;
  11. }
  12. /** * 创建缓存数据 * * @param t 缓存的数据,可以是任意类型的对象 * @param expire 过期时间,单位是秒 * @param load 数据装载器 */
  13. ExpireCacheData(T t, int expire, Supplier<T> load) {
  14. this(t, expire);
  15. this.load = load;
  16. }
  17. /** * 缓存的数据,可以是任意类型的对象 */
  18. public T data;
  19. /** * 过期时间 小于等于0标识永久存活 */
  20. public long expire;
  21. /** * 存活时间 */
  22. public long saveTime;
  23. /** * 还可以增加一个数据装载器,如果缓存中没有数据或者已经过期,则调用数据装载器加载最新的数据并且加入缓存,并返回。 */
  24. public Supplier<T> load;
  25. }

ExpireCache 管理器

我喜欢用继承,于是 ExpireCache 继承了 ConcurrentHashMap,本质上它依然是个 Map,自然有 get/put 方法,也相当于一个非常简单的内存缓存了。而且它本身承托了一个 static 对象 CACHE,这里权且当作单例使用。

  1. package com.ajaxjs.util.cache;
  2. import java.util.Date;
  3. import java.util.Timer;
  4. import java.util.TimerTask;
  5. import java.util.concurrent.ConcurrentHashMap;
  6. import java.util.function.Supplier;
  7. /** * 带过期时间的内存缓存。 参考:https://blog.csdn.net/wab719591157/article/details/78029861 * * @author sp42 frank@ajaxjs.com * */
  8. public class ExpireCache extends ConcurrentHashMap<String, ExpireCacheData<Object>> {
  9. private static final long serialVersionUID = 3850668473354271847L;
  10. /** * 单例,外界一般调用该对象的方法 */
  11. public final static ExpireCache CACHE = new ExpireCache();
  12. static {
  13. // 缓存自动失效
  14. // 周期性执行任务,另外可以考虑 ScheduledExecutorService
  15. new Timer().schedule(new TimerTask() {
  16. @Override
  17. public void run() {
  18. for (String key : CACHE.keySet()) {
  19. Object obj = CACHE.get(key);
  20. /* * 超时了,干掉!另外:有 load 的总是不会返回 null,所以这里不用考虑有 load 的 ExpireCacheData。 而且有 load * 的也不应该被 kill */
  21. if (obj == null)
  22. CACHE.remove(key);
  23. }
  24. }
  25. }, 0, 10 * 1000); // 10 秒钟清理一次
  26. }
  27. /** * 获取缓存中的数据 * * @param key 缓存 KEY * @return 缓存的数据,找不到返回或者过期则直接返回 null */
  28. public Object get(String key) {
  29. ExpireCacheData<Object> data = super.get(key);
  30. long now = new Date().getTime();
  31. if (data != null && (data.expire <= 0 || data.saveTime >= now))
  32. return data.data;
  33. else if (data.load != null) {
  34. Object value = data.load.get();
  35. data.data = value;
  36. data.saveTime = now + data.expire; // 重新算过存活时间
  37. return value;
  38. }
  39. return null;
  40. }
  41. /** * 获取缓存中的数据(避免强类型转换的麻烦) * * @param <T> 缓存的类型 * @param key 缓存 KEY * @param clz 缓存的类型 * @return 缓存的数据,找不到返回或者过期则直接返回 null */
  42. @SuppressWarnings({ "unchecked" })
  43. public <T> T get(String key, Class<T> clz) {
  44. Object obj = get(key);
  45. return obj == null ? null : (T) obj;
  46. }
  47. /** * * @param key 缓存 KEY * @param data 要缓存的数据 * @param expire 过期时间 */
  48. public void put(String key, Object data, int expire) {
  49. put(key, new ExpireCacheData<>(data, expire));
  50. }
  51. /** * * @param key 缓存 KEY * @param data 要缓存的数据 * @param expire 过期时间 * @param load 数据装载器,如果缓存中没有数据或者已经过期,则调用数据装载器加载最新的数据并且加入缓存,并返回 */
  52. public void put(String key, Object data, int expire, Supplier<Object> load) {
  53. put(key, new ExpireCacheData<>(data, expire, load));
  54. }
  55. }

例子

  1. import static org.junit.Assert.assertEquals;
  2. import static org.junit.Assert.assertNull;
  3. import org.junit.Test;
  4. import com.ajaxjs.util.cache.ExpireCache;
  5. public class TestCache {
  6. @Test
  7. public void testExpire() throws InterruptedException {
  8. ExpireCache.CACHE.put("foo", "bar", 3);
  9. Thread.sleep(1000);
  10. assertEquals("bar", ExpireCache.CACHE.get("foo"));
  11. Thread.sleep(2010);
  12. assertNull(ExpireCache.CACHE.get("foo"));
  13. ExpireCache.CACHE.put("bar", "foo", 2, () -> "foo-2");
  14. Thread.sleep(1000);
  15. assertEquals("foo", ExpireCache.CACHE.get("bar"));
  16. Thread.sleep(2010);
  17. assertEquals("foo-2", ExpireCache.CACHE.get("bar"));
  18. }
  19. }

发表评论

表情:
评论列表 (有 0 条评论,16人围观)

还没有评论,来说两句吧...

相关阅读