四、常用工具之Guava Cache

(一)介绍参见Guava Cache内存缓存使用实践-定时异步刷新及简单抽象封装

(二)使用样例
1.引入Jar包

<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
        </dependency>

2.抽象一个“cache”模板工具类

package com.imooc.util.cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 *     利用guava实现的内存缓存。缓存加载之后永不过期,后台线程定时刷新缓存值。刷新失败时将继续返回旧缓存。
 * 在调用getValue之前,需要设置 refreshDuration, refreshTimeunit, maxSize 三个参数后台刷新线程池为该系
 * 统中所有子类共享,大小为20.
 * @author **
 * @date 2018/6/8 15:07
 */
public abstract class BaseGuavaCache<K, V> {
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseGuavaCache.class);

    /**
     * 缓存自动刷新周期
     */
    protected int refreshDuration = 10;
    /**
     * 缓存自动刷新周期时间格式
     */
    protected TimeUnit refreshTimeUnit = TimeUnit.MINUTES;

    /**
     * 缓存过期周期(负数代表永不过期)
     */
    protected int expirationDuration = -1;
    /**
     * 缓存过期周期时间格式
     */
    protected TimeUnit expirationTimeUnit = TimeUnit.HOURS;
    /**
     * 缓存最大容量
     * 备注:maxiSize定义了缓存的容量大小,当缓存数量即将到达容量上线时,则会进行缓存回收,
     *       回收最近没有使用或总体上很少使用的缓存项。需要注意的是在接近这个容量上限时就
     *       会发生,所以在定义这个值的时候需要视情况适量地增大一点。
     */
    protected int maxSize = 4;
    /**
     * 缓存刷新线程池
     */
    protected static ListeningExecutorService  refreshThreadPool =
            MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(20));
    /**
     * 使用双重检查锁,设置为单例
     */
    private LoadingCache<K, V> cache = null;

    /**
     * 用于初始化缓存(某些场景下使用,例如系统启动检测缓存加载是否正常)
     */
    public abstract void loadValueWhenStarted();

    /**
     * 定义缓存值过期时的计算方法:一般是缓存过期时,重新读取数据库,缓存数据库中的数值(具体视情况而变)
     *     新值计算失败时抛出异常,get操作将继续返回旧的缓存
     * @param key 缓存的key
     * @return 缓存值
     * @throws Exception 异常
     */
    protected abstract V getValueWhenExpired(K key) throws Exception;

    private LoadingCache<K, V> getCache(){
        //单例模式中的双重检查锁
        if (cache == null){
            synchronized (this){
                if (cache == null){
                    CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder().maximumSize(maxSize);
                    //设置缓存刷新周期
                    if (refreshDuration > 0){
                        cacheBuilder.refreshAfterWrite(refreshDuration, refreshTimeUnit);
                    }
                    //设置缓存过期周期
                    if (expirationDuration > 0){
                        cacheBuilder.expireAfterWrite(expirationDuration, expirationTimeUnit);
                    }
                    cache = cacheBuilder.build(new CacheLoader<K, V>() {
                        @Override
                        public V load(K k) throws Exception {
                            //为null时,会抛出异常
                            return getValueWhenExpired(k);
                        }

                        @Override
                        public ListenableFuture<V> reload(final K key, V oldValue) throws Exception {
                            return refreshThreadPool.submit(new Callable<V>() {
                                @Override
                                public V call() throws Exception {
                                    return getValueWhenExpired(key);
                                }
                            });
                        }
                    });
                }
            }
        }
        return cache;
    }

    /**
     * 从cache中取数据
     * @param key 键
     * @return 值
     * @throws Exception 异常
     * 备注:该key对应的值在缓存中不存在或者过期,调用该方法就会抛出异常。这个方法必须显式抛出异常,
     *       以供业务层判断缓存是否存在以及是否过期
     */
    public V getValue(K key) throws Exception {
        try {
            return getCache().get(key);
        } catch (ExecutionException e){
            LOGGER.error("从内存缓存中获取内容时发生异常,key:" + key, e);
            throw e;
        }
    }

    /**
     * 从cache中取数据,若发生异常,则返回默认值
     * @param key 键
     * @return 值
     * @throws ExecutionException 异常
     */
    public V getValueOfDefault(K key, V defaultValue){
        try {
            return getCache().get(key);
        } catch (ExecutionException e) {
            LOGGER.error("从内存缓存中获取内容时发生异常,key:" + key, e);
            return defaultValue;
        }
    }
    public void put(K key, V value){
        cache.put(key, value);
    }

    /**
     * 设置缓存刷新周期(链式编程)
     */
    public BaseGuavaCache<K, V> setRefreshDuration(int refreshDuration) {
        this.refreshDuration = refreshDuration;
        return this;
    }
    /**
     * 设置缓存刷新周期时间单元(链式编程)
     */
    public void setRefreshTimeUnit(TimeUnit refreshTimeUnit) {
        this.refreshTimeUnit = refreshTimeUnit;
    }
    /**
     * 设置缓存过期周期(链式编程)
     */
    public void setExpirationDuration(int expirationDuration) {
        this.expirationDuration = expirationDuration;
    }
    /**
     * 设置缓存过期周期时间单元(链式编程)
     */
    public void setExpirationTimeUnit(TimeUnit expirationTimeUnit) {
        this.expirationTimeUnit = expirationTimeUnit;
    }
    /**
     * 设置缓存最大数量(链式编程)
     */
    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    /**
     * 清除所有缓存
     */
    public void clearAllCache(){
        this.getCache().invalidateAll();
    }
    /**
     * 清除指定缓存
     */
    public void clearCacheByKey(K key){
        this.getCache().invalidate(key);
    }

}

3.发送验证码缓存工具类

package com.imooc.util.cache;


import java.util.concurrent.TimeUnit;

/**
 * @author 潘畅
 * @date 2018/6/8 17:17
 */
public class CodeCache extends BaseGuavaCache<String, Object> {

    private static CodeCache codeCache;

    /**
     * 单例模式
     */
    public static CodeCache getInstance() {
        if (codeCache == null){
            synchronized (CodeCache.class){
                if (codeCache == null){
                    codeCache = new CodeCache();
                }
            }
        }
        return codeCache;
    }

    /**
     * 在这里初始化必要参数(比如过期时间,定期刷新时间,缓存最大条数等)
     */
    private CodeCache() {
        //初始化过期时间
        this.setExpirationDuration(1);
        this.setExpirationTimeUnit(TimeUnit.MINUTES);
        //不刷新缓存
        this.setRefreshDuration(-1);
        this.setMaxSize(1000);
    }

    @Override
    public void loadValueWhenStarted() {

    }

    /**
     * 关于这个方法,暂时我也不太清楚,只能暂时先调用“getValue(key)”方法
     */
    @Override
    protected Object getValueWhenExpired(String key) throws Exception {
        return getValue(key);
    }

}

4.业务使用

/**
     * 保存验证码进缓存
     * 备注:验证码的缓存是设置了过期时间的(1min),
     *           若“该手机号首次进入”或“该手机号对应的缓存已经过期”,此时调用“cache.get(key)”方法会抛出异常,
     *       此时需要保存手机号,验证码进缓存,返回true;
     *           若不抛出异常,则说明该缓存正处于有效期,用户在1min内重复请求发送验证码,无需保存进缓存,返回false
     * @param phone 手机号
     * @param verificationCode 验证码
     * @return 是否成功保存手机号、验证码进缓存
     */
    private boolean saveVerificationCode(String phone, String verificationCode) {
        CodeCache codeCache = CodeCache.getInstance();
        try {
            codeCache.getValue(phone);
            //若不报异常,说明已存在该缓存,且未过期,说明在1min内重复请求了
            return false;
        } catch (Exception e) {
            //当缓存过期或没有时,取值会报异常,说明此时可将验证码存入缓存
            codeCache.put(phone, verificationCode);
            return true;
        }
    }

猜你喜欢

转载自blog.csdn.net/panchang199266/article/details/80624466