享元模式-实现大颗粒度对象缓存机制

详解

享元模式是一种结构型设计模式,其主要目的是通过共享尽可能多的相同部分来有效地支持大量细粒度的对象。它通过将对象的属性分为内在属性(可以共享、不随环境变化的部分)和外在属性(根据场景变化、不能共享的部分),从而减少内存占用和提高性能。

核心思想:

享元模式通过复用对象,避免重复创建大量相似对象,从而节省内存。它通常适用于创建大量相似对象且这些对象包含可以共享的相同状态的场景。

享元模式的组成部分:

  1. 享元接口(Flyweight Interface):定义享元对象的接口,供外部使用。
  2. 具体享元类(Concrete Flyweight):实现享元接口,并存储共享的内部状态。
  3. 享元工厂(Flyweight Factory):负责创建和管理享元对象,确保相同的对象只被创建一次。
  4. 客户端(Client):使用享元对象,并存储外部状态(即不共享的部分)。

使用场景:

享元模式适用于以下场景:

  1. 大量相似对象的创建:当系统中需要创建大量相似或相同的对象时,例如大量的小文本、按钮、图形等。
  2. 内存占用过高:如果这些对象占用大量内存,且其中一部分状态是共享的,可以通过享元模式减少内存消耗。
  3. 对象的状态可以分离:对象的状态可以分为内部状态(可以共享)和外部状态(不能共享),并且外部状态可以由客户端代码显式传递。

如图所示:

例如 这种比较通用的,大颗粒度的,精彩需要被调用的对象,我们就可以使用享元模式,将它缓存起来

简单享元示例

1. 享元模式实现

首先,我们定义一个享元对象和一个享元工厂。

import java.util.HashMap;
import java.util.Map;// 享元接口
interface Flyweight {void operation(String extrinsicState);
}// 具体享元实现
class ConcreteFlyweight implements Flyweight {private final String intrinsicState;public ConcreteFlyweight(String intrinsicState) {this.intrinsicState = intrinsicState;}@Overridepublic void operation(String extrinsicState) {System.out.println("Intrinsic State: " + intrinsicState + ", Extrinsic State: " + extrinsicState);}
}// 享元工厂
class FlyweightFactory {private final Map<String, Flyweight> flyweights = new HashMap<>();public Flyweight getFlyweight(String intrinsicState) {if (!flyweights.containsKey(intrinsicState)) {flyweights.put(intrinsicState, new ConcreteFlyweight(intrinsicState));}return flyweights.get(intrinsicState);}public void updateFlyweight(String intrinsicState, String newState) {// 此处可以实现更新逻辑System.out.println("Updating Flyweight with intrinsic state: " + intrinsicState + " to new state: " + newState);flyweights.put(intrinsicState, new ConcreteFlyweight(newState)); // 替换为新状态}
}

2. 自定义注解和 AOP 实现

接下来,我们使用 Spring AOP 在进行增删改操作时更新享元工厂。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;// 定义自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UpdateFlyweight {String state();
}// 切面类
@Aspect
@Component
class FlyweightUpdateAspect {@Autowiredprivate FlyweightFactory flyweightFactory;@Pointcut("@annotation(updateFlyweight)")public void updatePointcut(UpdateFlyweight updateFlyweight) {}@AfterReturning(value = "updatePointcut(updateFlyweight)", argNames = "updateFlyweight", returning = "result")public void afterReturning(UpdateFlyweight updateFlyweight, Object result) {String intrinsicState = updateFlyweight.state();// 更新享元工厂flyweightFactory.updateFlyweight(intrinsicState, "New State Value");}
}

3. 使用示例

我们可以定义一个服务类,里面包含一些增删改操作,并使用自定义注解来标记。

import org.springframework.stereotype.Service;@Service
public class DataService {// 增加数据@UpdateFlyweight(state = "exampleState")public void addData() {System.out.println("Data added.");}// 删除数据@UpdateFlyweight(state = "exampleState")public void deleteData() {System.out.println("Data deleted.");}// 修改数据@UpdateFlyweight(state = "exampleState")public void updateData() {System.out.println("Data updated.");}
}

4. 启动 Spring Boot 应用

最后,使用 Spring Boot 启动应用,并在 main 方法中进行bean的调用。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;@SpringBootApplication
public class FlyweightApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(FlyweightApplication.class, args);DataService dataService = context.getBean(DataService.class);dataService.addData();    // 添加数据dataService.updateData();  // 修改数据dataService.deleteData();  // 删除数据}
}

场景实践

回到这张图,我想对大颗粒度的 这个对象做缓存,但是用户如果修改了 应发项 或者是 涉及到该domain对象的任何一项 我都要更新这个缓存,我还要给这个缓存设置缓存过期时间,并且重新读取缓存的代码 要易维护,毕竟这个设计者模式要通用,肯定不只是能为这一个domain对象服务

ok,我列一下,需要支持的功能:

1、缓存

2、缓存到期时间

3、数据的实时性,受到修改 每个实体要有自己的重新加载缓存策略

4、如果大颗粒度对象存太多,容易内存溢出需要加入限制,且每个实体限制 应根据业务场景调整

5、如果达到这个缓存上限 我应该踢掉一个,把新的放进来,该怎么踢

大家看到上面这些要求 是不是很苦恼,莫慌,我狂肝一整天 已经想到了解决方案 并且已经实现,请看如下结构:

调用示例

这样使用起来,是不是非常的简洁,清晰,明了


/*** @author shenwang* @description 缓存池使用范例* @date 2024-10-31 14:21*/
@Component
public class LargeObjectCachePoolExample {/*** 想用哪个对象,注入哪个对象的pool,pool位置:cn.txbd.infrastructure.gatewayimpl.cache.pool.impl文件下*/@Autowiredprivate CommissionTemplateCommonDOPool commissionTemplateCommonDOPool;/*** 查询佣金模板domain对象*/public void queryCommissionTemplateDO(String templateId){LargeObjectCacheResult<CommissionTemplateCommonDO> templateDO = commissionTemplateCommonDOPool.get(templateId);}/*** 修改佣金项* 修改了佣金项佣金模板基本信息会受到影响* @param templateId*/public void updateCommissionItem(String templateId){//todo 修改佣金项逻辑,此处省略100行代码//重新加载缓存commissionTemplateCommonDOPool.reload(templateId);}
}

如何扩展

当然使用起来很方便,那扩展起来 那是更方便啊!!!!!!!

ok,光说没用,请看代码:


/*** @author shenwang* @description CommissionTemplateCommonDO对象缓存池* @date 2024-10-30 17:13*/
@Component
public class CommissionTemplateCommonDOPool extends LargeObjectBasePool<CommissionTemplateCommonDO>  {public CommissionTemplateCommonDOPool() {super(LargeObjectCacheType.COMMISSION_TEMPLATE_COMMON_DO);}@Async@Overridepublic void reload(String key) {//todo 重新查询 CommissionTemplateCommonDO domain对象的实现CommissionTemplateCommonDO commissionTemplateCommonDO = new CommissionTemplateCommonDO();commissionTemplateCommonDO.setTemplateId(key);commissionTemplateCommonDO.setTemplateName("测试模版");//更新缓存this.put(key,commissionTemplateCommonDO, LargeObjectCacheConstant.DEFAULT_DELAY_TIME,LargeObjectCacheConstant.DEFAULT_DELAY_TIME_UNIT);}
}

如上图所示,只需要继承LargeObjectBasePool,并且重写一下reload方法就好了

核心LargeObjectBasePool

该类提供了方法:

1、put      放入缓存,可以自定义缓存失效时间,如果到达上限则会穷举 踢掉一个缓存,腾出空间

2、get      获取缓存,如果没有获取到 会去走一遍reload方法 并且给上默认的失效时间

3、remove  移除缓存


/*** @author shenwang* @description 大对象缓存池* @date 2024-10-30 17:14*/
@Slf4j
public abstract class LargeObjectBasePool<T extends Serializable> implements Serializable{private static final long serialVersionUID = 1L;/*** 定时任务调度(用于设置缓存对象的过去时间)*/private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();/*** 缓存的大对象类型*/public LargeObjectCacheType cacheType;/*** 缓存池*/public Map<String, T> pool;/*** 每个key被命中的次数*/public Map<String, Integer> hitCountMapping;public LargeObjectBasePool(LargeObjectCacheType cacheType) {this.cacheType = cacheType;// 初始化pool,使用提供的limit作为初始容量this.pool = new HashMap<>(cacheType.getLimit());this.hitCountMapping = new HashMap<>(cacheType.getLimit());}/*** 重新加载缓存数据* 抽象方法,让子类重写* @param key* @return*/public abstract void reload(String key);/*** 获取缓存池中的对象* @param key 缓存的键* @return 返回对应类型的 LargeObjectCacheResult*/public LargeObjectCacheResult<T> get(String key) {// 从缓存池中获取对象T t = pool.get(key);if (ObjectUtil.isNotNull(t)) {// 命中次数+1hitCountIncrementOne(key);log.info("LargeObjectBasePool,类型:{}, 获取到了缓存数据, key:{},命中次数:{},当前keyValues:{}",cacheType.getDesc(), key, hitCountMapping.get(key),JSONUtil.toJsonStr(pool.keySet()));return createResult(key, t);}// 如果在缓存池中没有拿到,则重新获取一次reload(key);t = pool.get(key);if (ObjectUtil.isNull(t)) {log.warn("LargeObjectBasePool,类型:{},未能重新加载缓存对象,key:{}", cacheType.getDesc(), key);return null;}// 将重新获取的对象放入缓存池this.put(key, t, LargeObjectCacheConstant.DEFAULT_DELAY_TIME,LargeObjectCacheConstant.DEFAULT_DELAY_TIME_UNIT);// 返回深拷贝对象return createResult(key, t);}/*** 放入缓存* @param key* @param obj*/public synchronized  void put(String key, T obj, long delay, TimeUnit timeUnit) {// 如果超过了限制,则去移除一个缓存,腾出空间if (pool.size() >= cacheType.getLimit()) {//穷举出 命中率第二小的key,如果没有 则移除第一个keyString removeKey = findSecondMinHitCountKey();if (StringUtil.verifyEmptyString(removeKey)){removeKey = pool.keySet().iterator().next();}//移除缓存,key 对应的命中次数pool.remove(removeKey);hitCountMapping.remove(removeKey);}pool.put(key, deepCopy(obj));hitCountMapping.put(key, 0);// 创建一个任务,在指定延迟后移除键值对scheduler.schedule(() -> {this.remove(key);log.info("被移除的key: {}",key);}, delay, timeUnit);}/*** 移除缓存* @param key*/public synchronized void remove(String key){//移除缓存,key 对应的命中次数pool.remove(key);hitCountMapping.remove(key);}/*** 命中次数+1* @param key*/private void hitCountIncrementOne(String key) {Integer hitCount = hitCountMapping.get(key);if (ObjectUtil.isNull(hitCount) || hitCount < 1) {hitCount = 1;} else {hitCount++;}hitCountMapping.put(key, hitCount);}/*** 穷举出命中率第二低的key* @return*/private String findSecondMinHitCountKey() {// 如果没有足够的记录,返回nullif (hitCountMapping.size() < 2) {return null;}// 初始化最小和第二小的hitCount为最大整数int minHitCount = Integer.MAX_VALUE;int secondMinHitCount = Integer.MAX_VALUE;String minKey = null;String secondMinKey = null;for (Map.Entry<String, Integer> entry : hitCountMapping.entrySet()) {int hitCount = entry.getValue();if (hitCount < minHitCount) {// 更新第二小和最小的hitCountsecondMinHitCount = minHitCount;secondMinKey = minKey;minHitCount = hitCount;minKey = entry.getKey();} else if (hitCount < secondMinHitCount && hitCount != minHitCount) {// 更新第二小的hitCountsecondMinHitCount = hitCount;secondMinKey = entry.getKey();}}return secondMinKey;}/*** 获取缓存类型* @return*/public LargeObjectCacheType getCacheType() {return cacheType;}/*** 创建并返回 LargeObjectCacheResult 对象* @param key 缓存的键* @param obj 缓存的对象* @return 返回 LargeObjectCacheResult 对象*/private LargeObjectCacheResult<T> createResult(String key, T obj) {return new LargeObjectCacheResult<>(key, deepCopy(obj), hitCountMapping.get(key));}/*** 深拷贝方法,使用序列化与反序列化实现深拷贝* @param obj* @return*/@SuppressWarnings("unchecked")protected T deepCopy(T obj) {try {ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(bos);out.writeObject(obj);out.flush();ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream in = new ObjectInputStream(bis);return (T) in.readObject();} catch (NotSerializableException e) {log.error("对象未实现Serializable接口: {}", e.getMessage());throw new RuntimeException("深拷贝失败: 对象未实现Serializable接口", e);} catch (IOException e) {log.error("IOException during deep copy: {}", e.getMessage());throw new RuntimeException("深拷贝失败: IO异常", e);} catch (ClassNotFoundException e) {log.error("ClassNotFoundException during deep copy: {}", e.getMessage());throw new RuntimeException("深拷贝失败: 类未找到", e);}}}

并且采用深拷贝,避免使用缓存对象时,缓存对象被踢除

通用代码


/*** @author shenwang* @description 常量* @date 2024-10-31 14:45*/
public class LargeObjectCacheConstant {/*** 默认过期时间*/public static final long DEFAULT_DELAY_TIME = 2;/*** 默认过期时间单位*/public static final TimeUnit DEFAULT_DELAY_TIME_UNIT = TimeUnit.HOURS;}/*** @author shenwang* @description 大对象缓存类型* @date 2024-10-30 16:42*/
@Getter
@AllArgsConstructor
public enum LargeObjectCacheType {COMMISSION_TEMPLATE_COMMON_DO(0, CommissionTemplateCommonDO.class,10,"佣金模板共用对象(CommissionTemplateCommonDO.class)");/*** code 类型唯一标识*/public Integer code;/*** 对应的类*/public Class clazz;/*** 缓存上限*/public Integer limit;/*** 描述*/public String desc;public static LargeObjectCacheType getByCode(Integer code){for (LargeObjectCacheType type : LargeObjectCacheType.values()){if (type.getCode().intValue() == code.intValue()){return type;}}return null;}
}/*** @author shenwang* @description 公用的大对象返回结果* @date 2024-10-31 09:50*/
@Data
public class LargeObjectCacheResult<T> {private String key;private T obj;private Integer hitCount;public LargeObjectCacheResult(String key,T obj,Integer hitCount){this.key = key;this.obj = obj;this.hitCount = hitCount;}
}

OK,今天的分享就到这里喽,感兴趣的小伙伴 可以copy一下场景实践的代码自己去试试效果,本人亲测效果很不错,有问题,或者更好的解决方案可以在评论区留言哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/57598.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter学习笔记(一)-----环境配置

一、android 环境 android这边可以参照godot的配置 1.装java Java Downloads | Oracle x64 Compressed Archive &#xff1a;下载后直接解压到某个位置&#xff0c;不用安装 x64 installer: 下载后双击安装 注意&#xff1a;不要去百度直接搜Java安装&#xff0c;这样你最多安…

JetBrains Clion Idea 等缓存文件和配置文件迁移

JetBrains 缓存文件和配置文件迁移 文件默认路径 缓存文件默认路径&#xff1a; %userprofile%/AppData/Local/JetBrains/应用名 如 C:/Users/wbl/AppData/Local/JetBrains/CLion2021.3日志文件默认路径&#xff1a;默认在配置文件目录下的log文件夹 %userprofile%/AppData…

《AI产品经理手册》——解锁AI时代的商业密钥

在当今这个日新月异的AI时代&#xff0c;每一位产品经理都面临着前所未有的挑战与机遇&#xff0c;唯有紧跟时代潮流&#xff0c;深入掌握AI技术的精髓&#xff0c;才能在激烈的市场竞争中独占鳌头。《AI产品经理手册》正是这样一部为AI产品经理量身定制的实战宝典&#xff0c;…

2024年最全2024年最系统的网络安全自学路线,学完即可就业_安全学习路线(2),2024年最新你掌握了多少

一个人可以走的很快&#xff0c;但一群人才能走的更远&#xff01;不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人&#xff0c;都欢迎加入我们的的圈子&#xff08;技术交流、学习资源、职场吐槽、大厂内推、面试辅导&#xff09;&#xff0c;让我们一起学习成长&#xf…

前端拖拽库方案之react-beautiful-dnd

近期&#xff0c;知名 React 拖拽库 react-beautiful-dnd 宣布了项目弃用的决定&#xff0c;未来将不再维护。这一决定源于其存在的缺陷与局限性&#xff0c;促使作者转向开发一个更加现代化的拖拽解决方案——Pragmatic drag and drop&#xff08;下面会介绍&#xff09;&…

【深度学习】实验 — 动手实现 GPT【四】:代码实现Transformer、代码实现GPT模型、训练大型语言模型(LLM)

【深度学习】实验 — 动手实现 GPT【四】&#xff1a;代码实现Transformer、代码实现GPT模型、训练大型语言模型&#xff08;LLM&#xff09; 在 Transformer 块中连接注意力层和线性层代码实现Transformer 块 代码实现GPT模型文本生成训练模型计算训练集和验证集的损失 训练大…

我在命令行下剪辑视频

是的&#xff0c;你不需要格式工厂&#xff0c;你也不需要会声会影&#xff0c;更不需要爱剪辑这些莫名其妙的流氓软件&#xff0c;命令行下视频处理&#xff0c;包括剪辑&#xff0c;转码&#xff0c;提取&#xff0c;合成&#xff0c;缩放&#xff0c;字幕&#xff0c;特效等…

海外云手机是什么?对外贸电商有什么帮助?

在外贸电商领域&#xff0c;流量引流已成为卖家们关注的核心问题。越来越多的卖家开始利用海外云手机&#xff0c;通过TikTok等社交平台吸引流量&#xff0c;以推动商品在海外市场的销售。那么&#xff0c;海外云手机到底是什么&#xff1f;它又能为外贸电商卖家提供哪些支持呢…

MATLAB绘图|关于三维制图,给初学者的建议

给MATLAB的关于绘制三维图的建议 文章目录 基础知识使用基本函数设置轴标签和标题调整视角添加网格和图例绘制子图灵活使用 hold on 和 hold off保存图形总结 基础知识 了解三维坐标系统&#xff1a;三维图形有三个轴&#xff08;x、y、z&#xff09;&#xff0c;确保你理解如…

centos7配置keepalive+lvs

拓扑图 用户访问www.abc.com解析到10.4.7.8&#xff0c;防火墙做DNAT将访问10.4.7.8:80的请求转换到VIP 172.16.10.7:80&#xff0c;负载均衡器再将请求转发到后端web服务器。 实验环境 VIP&#xff1a;负载均衡服务器的虚拟ip地址 LB &#xff1a;负载均衡服务器 realserv…

opencv python笔记

OpenCV课程 OpenCV其实就是一堆C和C语言的源代码文件,这些源代码文件中实现了许多常用的计算机视觉算法。 OpenCV的全称是Open Source Computer Vision Library,是一个开放源代码的计算机视觉库OpenCV最初由英特尔公司发起并开发,以BSD许可证授权发行,可以在商业和研究领域中…

金融标准体系

目录 基本原则 标准体系结构图 标准明细表 金融标准体系下载地址 基本原则 需求引领、顶层设计。 坚持目标导向、问题导向、结果 导向有机统一&#xff0c;构建支撑适用、体系完善、科学合理的金融 标准体系。 全面系统、重点突出。 以金融业运用有效、保护有力、 管理高…

(实战)WebApi第10讲:Swagger配置、RESTful与路由重载

一、Swagger配置 1、导入SwashBuckle.AspNetCore包 2、在.NET Core 5框架里的startup.cs文件里配置swagger 3、在.NET Core 6框架里的Program.cs文件里配置swagger 二、RESTful风格&#xff1a;路由重载&#xff0c;HttpGet()括号中加参数 &#xff08;1&#xff09;原则&…

[java][基础]JSP

目标&#xff1a; 理解 JSP 及 JSP 原理 能在 JSP中使用 EL表达式 和 JSTL标签 理解 MVC模式 和 三层架构 能完成品牌数据的增删改查功能 1&#xff0c;JSP 概述 JSP&#xff08;全称&#xff1a;Java Server Pages&#xff09;&#xff1a;Java 服务端页面。是一种动态的…

数智税务 | 数电票:带来税务管理五大新挑战、绘就智慧税务征管新蓝图

目录 数电票&#xff0c;带来税务管理五大新挑战 1“集全” 2“管全” 3“算全” 4“备全” 5“控全” 数电票&#xff0c;绘就智慧税务征管新蓝图 1两化 2三端 3四融合 4变革征管方式 5优化征管流程 6提升征管效能 结语 数电票&#xff0c;带来税务管理五大新挑…

氢氧化铝改性打散机、分散机、包覆机、球磨机

表面改性是指在氢氧化铝颗粒表面吸附或包覆一层或多层物质&#xff0c;以改变其表面性质&#xff0c;增强其与基体材料的相容性和界面结合力。 表面改性方法主要分为物理法和化学法&#xff1a; 1.物理法&#xff1a;使用表面活性剂如高级脂肪酸、醇、胺、酯等进行表面包覆处…

【深度学习】Bert下载和使用(以bert-base-uncased为例)

【深度学习】Bert下载和使用&#xff08;以bert-base-uncased为例&#xff09; 代码报错报错原因解决方法解决步骤1.进入Hugging Face&#xff0c;检索bert-base-uncased2.点击Files and versions3.下载文件4.下载的文件放入文件夹5.代码修改 代码报错 bert BertModel.from_p…

JupyterLab,极其强大的下一代notebook!

JupyterLab简介 JupyterLab是Jupyter主打的最新数据科学生产工具&#xff0c;某种意义上&#xff0c;它的出现是为了取代Jupyter Notebook。不过不用担心Jupyter Notebook会消失&#xff0c;JupyterLab包含了Jupyter Notebook所有功能。 JupyterLab作为一种基于web的集成开发环…

MS01SF1 精准测距UWB模组助力露天采矿中的人车定位安全和作业效率提升

在当今矿业行业&#xff0c;随着全球对资源需求的不断增加和开采难度的逐步提升&#xff0c;传统的作业方式面临着越来越多的挑战。露天矿山开采&#xff0c;因其大规模的作业环境和复杂的地形特点&#xff0c;面临着作业人员的安全风险、设备调度的高难度以及资源利用率低下等…

使用 Github 进行项目管理

GitHub 是一个广泛使用的代码托管和协作平台&#xff0c;它提供了强大的工具来支持项目管理和团队协作。在项目开发和工作中&#xff0c;避免不了 Github 的使用&#xff0c;然鹅我一直没有稍微系统地学习过 github 的整个工作流程&#xff0c;对这些操作都是一知半解的&#x…