Bootstrap

Java设计模式如何优雅的使用本地缓存?

一、为什么要选择 guava cache

1、缓存Cache和ConcurrentMap虽然类似,但又不完全一样。最根本的区别是,ConcurrentMap会保存所有添加到其中的元素直到它们被明确的移除。而Cache通常可以配置一个自动化的回收策略去限制它的内存空间。

2、如果你还需要缓存满足以下几点要求

(1)、如果你打算牺牲更多内存来换取速度的提升。

(2)、缓存中的数据会频繁的被使用到。

(3)、Guava Cache只会把数据存储在内存中(Guava Cache是把数据存储于你运行的单个应用上,它不会把数据存储在文件或外部的服务器上)。

二、设计要求

1、需要满足为不同业务对象灵活创建缓存。

2、有效减少不同业务对象创建缓存, 查询缓存, 设置缓存这些步骤的代码冗余。

3、不同业务对象查询缓存业务代码与通用代码解偶。

三、常规用法

如下代码示例是guava缓存的常规用法, 顺序是先调用 CacheBuilder 的 newBuilder()方法, 然后构建出一个缓存对象, 可以看出, 如果每个需要缓存的业务对象都这样使用的话, 代码会非常臃肿, 并且例如判断是否为空等代码都是一模一样的. 没有必要每次都写一遍.

        // 常规方式调用
        Cache> baseCache = CacheBuilder.newBuilder().maximumSize(500).expireAfterAccess(7, TimeUnit.DAYS).build();
        String key = "123456";
        Optional baseOptional = null;
        if(StringUtils.isNotBlank(key)) {
            baseOptional = baseCache.get(key, () -> {
                System.out.println("[App]-[main]--------------> 没有命中缓存, 执行业务查询");
                System.out.println("[App]-[main]--------------> 查询逻辑.... 此处我为了简便, 直接new 了一个user对象");
                User user = new User(key, "张音乐");
                System.out.println("[App]-[main]--------------> 查询结束");
                return Optional.of(user);
            });
            baseCache.put(key, baseOptional);
            System.out.println("[App]-[main]--------------> base=" + JSONObject.toJSONString(baseOptional.get()));
        }

四、设计思路

1、利用模板方法设计模式来实现业务代码剥离, 抽取出通用模板。

2、如果缓存中没有数据, 则执行业务方法进行查询, 可以看下面的示例代码。service参数实际上是一个接口, 具体业务代码通过interface参数的形式传递进来执行, 实现解偶。

    /**
     * 查询
     * 如果缓存中没有数据, 则执行业务方法进行查询
     * @param key
     * @param service
     * @return
     */
    public Optional query(String key, ICache service) {
        try{
            if(StringUtils.isBlank(key)) {
                return Optional.empty();
            }
            return cacheHolder.get(key, () -> service.query(key));
        }catch (Exception e) {
            e.printStackTrace();
        }
        return Optional.empty();
    }

五、完整代码

引用依赖

        
            com.google.guava
            guava
            29.0-jre
        
 
        
            com.alibaba
            fastjson
            1.2.68
        

缓存模板

package com.biubiu.cache;
 
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.lang3.StringUtils;
 
import java.util.Optional;
import java.util.concurrent.TimeUnit;
 
/**
 * @author :张音乐
 * @date :Created in 2021/5/20 上午9:14
 * @description:本地缓存
 * @email: zhangyule1993@sina.com
 * @version: 1.0
 */
public class LocalCache {
    /**
     * guava cache
     */
    private Cache> cacheHolder;
 
    private int maximumSize = 500;
 
    private int duration = 7;
 
    /**
     * 配置缓存参数
     * @param size
     * @param duration
     * @return
     */
    public LocalCache setParameters(int size, int duration) {
        this.maximumSize = size;
        this.duration = duration;
        return this;
    }
 
 
    /**
     * 构建一个缓存
     * @return
     */
    public LocalCache build() {
        cacheHolder = CacheBuilder.newBuilder().maximumSize(maximumSize).expireAfterAccess(duration, TimeUnit.DAYS).build();
        return this;
    }
 
    /**
     * 查询
     * 如果缓存中没有数据, 则执行业务方法进行查询
     * @param key
     * @param service
     * @return
     */
    public Optional query(String key, ICache service) {
        try{
            if(StringUtils.isBlank(key)) {
                return Optional.empty();
            }
            Optional value = cacheHolder.get(key, () -> service.query(key));
            // 设置进入缓存
            put(key, value);
            return value;
        }catch (Exception e) {
            e.printStackTrace();
        }
        return Optional.empty();
    }
 
 
    /**
     * 把值推到缓存中
     * @param key
     * @param optional
     */
    public void put(String key, Optional optional) {
        cacheHolder.put(key, optional);
    }
 
 
    /**
     * 通用接口, 利用模板方法设计模式, 将业务方法抽取出来. 不同的业务 传递不同的查询逻辑.
     * @param 
     */
    public interface ICache {
        /**
         * 通用接口
         * @param key
         * @return
         */
        Optional query(String key);
    }
}

六、使用示例

package com.biubiu.cache;
 
import com.alibaba.fastjson.JSONObject;
 
import java.math.BigDecimal;
import java.util.Optional;
 
/**
 * @author :张音乐
 * @date :Created in 2021/5/20 上午9:25
 * @description:demo
 * @email: zhangyule1993@sina.com
 * @version: 1.0
 */
public class App {
 
    /**
     * 用户实体
     */
    static class User {
 
        private String userId;
 
        private String username;
 
        public User() {
        }
 
        public User(String userId, String username) {
            this.userId = userId;
            this.username = username;
        }
 
        public String getUserId() {
            return userId;
        }
 
        public void setUserId(String userId) {
            this.userId = userId;
        }
 
        public String getUsername() {
            return username;
        }
 
        public void setUsername(String username) {
            this.username = username;
        }
    }
 
    /**
     * 订单实体
     */
    static class Order {
        private String orderId;
 
        private BigDecimal number;
 
        public Order() {
        }
 
        public Order(String orderId, BigDecimal number) {
            this.orderId = orderId;
            this.number = number;
        }
 
        public String getOrderId() {
            return orderId;
        }
 
        public void setOrderId(String orderId) {
            this.orderId = orderId;
        }
 
        public BigDecimal getNumber() {
            return number;
        }
 
        public void setNumber(BigDecimal number) {
            this.number = number;
        }
    }
 
    public static void main(String[] args) {
        // 创建一个用户缓存
        LocalCache userCache = new LocalCache().setParameters(500, 7).build();
        String userId = "123456";
        //查询, 验证缓存中每有缓存的时候是从哪里进行查询的
        Optional userOptional = userCache.query(userId, user -> getUserById(userId));
        System.out.println("[App]-[main]--------------> user=" + JSONObject.toJSONString(userOptional.get()));
 
        // 再验证数据是从缓存中查询还是从业务中查询
        userOptional = userCache.query(userId, user -> getUserById(userId));
        System.out.println("[App]-[main]--------------> user=" + JSONObject.toJSONString(userOptional.get()));
 
        System.out.println();
        // 创建一个订单缓存
        LocalCache orderCache = new LocalCache().setParameters(500, 7).build();
        String orderId = "TB123456";
        //查询, 验证缓存中每有缓存的时候是从哪里进行查询的
        Optional orderOptional = orderCache.query(orderId, order -> getOrderById(orderId));
        System.out.println("[App]-[main]--------------> order=" + JSONObject.toJSONString(orderOptional.get()));
        // 再验证数据是从缓存中查询还是从业务中查询
        orderOptional = orderCache.query(orderId, order -> getOrderById(orderId));
        System.out.println("[App]-[main]--------------> order=" + JSONObject.toJSONString(orderOptional.get()));
 
    }
 
 
    private static Optional getUserById(String userId) {
        System.out.println("[App]-[getUserById]--------------> 没有命中缓存, 执行业务查询");
        System.out.println("[App]-[getUserById]--------------> 查询逻辑.... 此处我为了简便, 直接new 了一个user对象");
        User user = new User(userId, "张音乐");
        System.out.println("[App]-[getUserById]--------------> 查询结束");
        return Optional.of(user);
    }
 
    private static Optional getOrderById(String orderId) {
        System.out.println("[App]-[getOrderById]--------------> 没有命中缓存, 执行业务查询");
        System.out.println("[App]-[getOrderById]--------------> 查询逻辑.... 此处我为了简便, 直接new 了一个order对象");
        Order order = new Order(orderId, new BigDecimal("9.9"));
        System.out.println("[App]-[getOrderById]--------------> 查询结束");
        return Optional.of(order);
    }
}

七、运行截图

从图中可以看出, 第一次没有命中缓存, 执行了业务查询, 第二次命中了缓存, 从缓存中获取的数据。