无法使用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();
    }
}

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐