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

详解

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

核心思想:

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

享元模式的组成部分:

  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,一经查实,立即删除!

相关文章

【系统设计】Merkle 算法在 Git 中的应用:深入理解与实践

引言 在现代软件开发中&#xff0c;Git 已成为版本控制的事实标准。Git 能够快速处理项目的变化&#xff0c;确保代码的完整性&#xff0c;其中一个关键技术就是 Merkle 树。本文将深入探讨 Merkle 算法的原理&#xff0c;以及其在 Git 中的具体应用。 1. Merkle 算法的原理 …

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;…

uniapp中skymap.html(8100端口)提示未登录的排查与解决方法

问题&#xff1a; 目前账号已经登录&#xff0c;uniapp的其他端口均可以访问到数据&#xff0c;唯独skymap.html中的8100会提示未登录。&#xff08;8100是后端网关gateway端口&#xff09; 分析&#xff1a; 在 skymap.html 中遇到未登录提示的问题&#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许可证授权发行,可以在商业和研究领域中…

spring-boot(整合jdbc)

JDBC JDBC 的全称是Java数据库连接(Java Database Connectivity,简称JDBC),是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口。 提供了诸如查询和更新数据库中数据的方法,JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。 JDBC API …

金融标准体系

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

keil编译报错:sys_timeout: timeout != NULL, pool MEMP_SYS_TIMEOUT is empty

文章目录 一、问题背景二、可能原因内存池配置不当&#xff1a;内存泄漏&#xff1a;并发访问冲突&#xff1a; 三、解决方案优化内存池配置&#xff1a;检查并修复内存泄漏&#xff1a;加强并发访问控制&#xff1a;优化代码设计&#xff1a; 四、总结 STM32 sys_timeout: tim…

(实战)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;原则&…

超子物联网HAL库笔记:[汇总]

介绍 此笔记为观看B站UP&#xff1a;超子说物联网所写&#xff0c;感谢老师。老师的架构真的特别好&#xff01; 我在学习HAL库之前有标准库基础&#xff0c;所以学习稍快&#xff0c;但会尽量详细记录 笔记和项目文件在gitee开源了 大家可以在我的gitee仓库中下载笔记源文…

[java][基础]JSP

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