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 加入
还未添加个人简介
评论