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

一、为什么要选择 guava cache
1、缓存 Cache 和 ConcurrentMap 虽然类似,但又不完全一样。最根本的区别是,ConcurrentMap 会保存所有添加到其中的元素直到它们被明确的移除。而 Cache 通常可以配置一个自动化的回收策略去限制它的内存空间。
2、如果你还需要缓存满足以下几点要求
(1)、如果你打算牺牲更多内存来换取速度的提升。
(2)、缓存中的数据会频繁的被使用到。
(3)、Guava Cache 只会把数据存储在内存中(Guava Cache 是把数据存储于你运行的单个应用上,它不会把数据存储在文件或外部的服务器上)。
二、设计要求
1、需要满足为不同业务对象灵活创建缓存。
2、有效减少不同业务对象创建缓存, 查询缓存, 设置缓存这些步骤的代码冗余。
3、不同业务对象查询缓存业务代码与通用代码解偶。
三、常规用法
如下代码示例是 guava 缓存的常规用法, 顺序是先调用 CacheBuilder 的 newBuilder()方法, 然后构建出一个缓存对象, 可以看出, 如果每个需要缓存的业务对象都这样使用的话, 代码会非常臃肿, 并且例如判断是否为空等代码都是一模一样的. 没有必要每次都写一遍.
// 常规方式调用 Cache<String, Optional<User>> baseCache = CacheBuilder.newBuilder().maximumSize(500).expireAfterAccess(7, TimeUnit.DAYS).build(); String key = "123456"; Optional<User> 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<T> query(String key, ICache<T> service) { try{ if(StringUtils.isBlank(key)) { return Optional.empty(); } return cacheHolder.get(key, () -> service.query(key)); }catch (Exception e) { e.printStackTrace(); } return Optional.empty(); }五、完整代码
引用依赖
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>29.0-jre</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.68</version> </dependency>缓存模板
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<T> { /** * guava cache */ private Cache<String, Optional<T>> cacheHolder; private int maximumSize = 500; private int duration = 7; /** * 配置缓存参数 * @param size * @param duration * @return */ public LocalCache<T> setParameters(int size, int duration) { this.maximumSize = size; this.duration = duration; return this; } /** * 构建一个缓存 * @return */ public LocalCache<T> build() { cacheHolder = CacheBuilder.newBuilder().maximumSize(maximumSize).expireAfterAccess(duration, TimeUnit.DAYS).build(); return this; } /** * 查询 * 如果缓存中没有数据, 则执行业务方法进行查询 * @param key * @param service * @return */ public Optional<T> query(String key, ICache<T> service) { try{ if(StringUtils.isBlank(key)) { return Optional.empty(); } Optional<T> 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<T> optional) { cacheHolder.put(key, optional); } /** * 通用接口, 利用模板方法设计模式, 将业务方法抽取出来. 不同的业务 传递不同的查询逻辑. * @param <T> */ public interface ICache<T> { /** * 通用接口 * @param key * @return */ Optional<T> 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<User> userCache = new LocalCache<User>().setParameters(500, 7).build(); String userId = "123456"; //查询, 验证缓存中每有缓存的时候是从哪里进行查询的 Optional<User> 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<Order> orderCache = new LocalCache<Order>().setParameters(500, 7).build(); String orderId = "TB123456"; //查询, 验证缓存中每有缓存的时候是从哪里进行查询的 Optional<Order> 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<User> 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<Order> 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); }}七、运行截图
从图中可以看出, 第一次没有命中缓存, 执行了业务查询, 第二次命中了缓存, 从缓存中获取的数据。
版权声明: 本文为 InfoQ 作者【张音乐】的原创文章。
原文链接:【http://xie.infoq.cn/article/e33acf337e4d1ed1eb146314d】。未经作者许可,禁止转载。
张音乐
求你关注我,别不识抬举.别逼我跪下来求你. 2021.03.28 加入
还未添加个人简介











评论