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

详解

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

核心思想:

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

享元模式的组成部分:

  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.rhkb.cn/news/465218.html

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

相关文章

HTML 基础标签——结构化标签<html>、<head>、<body>

文章目录 1. <html> 标签2. <head> 标签3. <body> 标签4. <div> 标签5. <span> 标签小结 在 HTML 文档中&#xff0c;使用特定的结构标签可以有效地组织和管理网页内容。这些标签不仅有助于浏览器正确解析和渲染页面&#xff0c;还能提高网页的可…

新华三H3CNE网络工程师认证—VLAN的配置

VLAN&#xff08;虚拟局域网&#xff09;是一种在逻辑上划分网络的技术&#xff0c;它可以将一个物理网络分割成多个虚拟网络&#xff0c;从而实现不同组的设备之间的隔离。在配置VLAN时&#xff0c;通常涉及到三种端口类型&#xff1a;Access、Trunk和Hybrid。Access端口用于连…

R语言*号标识显著性差异判断组间差异是否具有统计意义

前言 该R代码用于对Iris数据集进行多组比较分析&#xff0c;探讨不同鸢尾花品种在不同测量变量&#xff08;花萼和花瓣长度与宽度&#xff09;上的显著性差异。通过将数据转换为长格式&#xff0c;并利用ANOVA和Tukey检验&#xff0c;代码生成了不同品种间的显著性标记&#x…

手边酒店多商户版V2源码独立部署_博纳软云

新版采用laraveluniapp开发&#xff0c;为更多平台小程序开发提供坚实可靠的底层架构基础。后台UI全部重写&#xff0c;兼容手机端管理。 全新架构、会员卡、钟点房、商城、点餐、商户独立管理

Multi Agents协作机制设计及实践

01 多智能体协作机制的背景概述 在前述博客中&#xff0c;我们利用LangChain、AutoGen等开发框架构建了一个数据多智能体的平台&#xff0c;并使用了LangChain的Multi-Agents框架。然而&#xff0c;在实施过程中&#xff0c;我们发现现有的框架存在一些局限性&#xff0c;这些…

ReactPress—基于React的免费开源博客CMS内容管理系统

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎提出宝贵的建议&#xff0c;感谢Star。 ![ReactPress](https://i-blog.csdnimg.cn/direct/0720f155edaa4eadba796f4d96d394d7.png#pic_center ReactPress 是使用React开发的开源发布平台&…

如何在一个 Docker 容器中运行多个进程 ?

在容器化的世界里&#xff0c;Docker 彻底改变了开发人员构建、发布和运行应用程序的方式。Docker 容器封装了运行应用程序所需的所有依赖项&#xff0c;使其易于跨不同环境一致地部署。然而&#xff0c;在单个 Docker 容器中管理多个进程可能具有挑战性&#xff0c;这就是 Sup…

【JavaEE初阶 — 多线程】线程安全问题 & synchronized

目录 1. 什么是线程安全问题 (1) 观察线程不安全 (2) 线程安全的概念 2. 造成线程安全的原因 (1)线程调度的随机性 问题描述 解决方案 (2)修改共享数据&#xff06;原子性问题 问题描述 解决方案 3.synchronized 关键字 1. synchronized 的特性 (1) …

产品经理的重要性

一直觉得产品经理很重要&#xff0c;这几年写了好几篇和产品经理相关的思考。2020年写过对产品经理的一些思考的文章&#xff0c;2021年&#xff0c;写了一篇对如何分析项目的思考&#xff0c;2024年写了如何与PM探讨项目。 今天还想再写一篇&#xff0c;主要是最近很有感慨。…

Hunyuan-Large:推动AI技术进步的下一代语言模型

腾讯近期推出了基于Transformer架构的混合专家&#xff08;MoE&#xff09;模型——Hunyuan-Large&#xff08;Hunyuan-MoE-A52B&#xff09;。该模型目前是业界开源的最大MoE模型之一&#xff0c;拥有3890亿总参数和520亿激活参数&#xff0c;展示了极强的计算能力和资源优化优…

【Linux系列】利用 CURL 发送 POST 请求

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

通义灵码实操—飞机大战游戏

通义灵码实操—飞机大战游戏 有没有想象过自己独立编写一个有趣的小游戏。在本实践课程中&#xff0c;你不仅可以实现这个想法&#xff0c;而且还将得到通义灵码智能编程助手的支持与指导。我们将携手步入编程的神奇世界&#xff0c;以一种简洁、高效且具有创造性的方式&#…

lora训练模型 打造个人IP

准备工作 下载秋叶炼丹器整理自己的照片下载底膜 https://rentry.org/lycoris-experiments 实操步骤 解压整合包 lora-scripts,先点击“更新” 训练图片收集 比如要训练一个自己头像的模型&#xff0c;就可以拍一些自己的照片&#xff08;20-50张&#xff0c;最少15张&…

Caffeine 手动策略缓存 put() 方法源码解析

BoundedLocalManualCache put() 方法源码解析 先看一下BoundedLocalManualCache的类图 com.github.benmanes.caffeine.cache.BoundedLocalCache中定义的BoundedLocalManualCache静态内部类。 static class BoundedLocalManualCache<K, V> implements LocalManualCache&…

Spring Boot框架下的教育导师匹配系统

第一章 绪论 1.1 选题背景 如今的信息时代&#xff0c;对信息的共享性&#xff0c;信息的流通性有着较高要求&#xff0c;尽管身边每时每刻都在产生大量信息&#xff0c;这些信息也都会在短时间内得到处理&#xff0c;并迅速传播。因为很多时候&#xff0c;管理层决策需要大量信…

Unity SRP学习笔记(二)

Unity SRP学习笔记&#xff08;二&#xff09; 主要参考&#xff1a; https://catlikecoding.com/unity/tutorials/custom-srp/ https://docs.unity.cn/cn/2022.3/ScriptReference/index.html 中文教程部分参考&#xff08;可选&#xff09;&#xff1a; https://tuncle.blog/c…

2024年10款超好用的企业防泄密软件|企业文件加密防泄密必备!

随着信息技术的迅速发展&#xff0c;企业面临的数据泄露风险越来越高。为了保护企业的敏感信息&#xff0c;防止数据泄露&#xff0c;企业防泄密软件应运而生。以下是2024年值得关注的10款企业防泄密软件&#xff0c;帮助企业有效保障数据安全。 1.安秉网盾 安秉网盾防泄密是一…

K8S flannel网络模式对比

K8S flannel网络模式对比 VXLAN 模式Host-GW 模式如何查看 Flannel 的网络模式?如何修改 Flannel 的网络模式?如何修改flannel vxlan端口?Flannel 是一个 Kubernetes 中常用的网络插件,用于在集群中的节点之间提供网络连接。Flannel 提供了多种后端实现方式,vxlan 和 host…

计算机网络:网络层 —— 移动 IP 技术

文章目录 IPv6IPv6 的诞生背景主要优势IPv6引进的主要变化 IPv6数据报的基本首部IPv6数据报首部与IPv4数据报首部的对比 IPv6数据报的拓展首部IPv6地址IPv6地址空间大小IPv6地址的表示方法 IPv6地址的分类从IPv4向IPv6过渡使用双协议栈使用隧道技术 网际控制报文协议 ICMPv6ICM…

大客户营销数字销售实战讲师培训讲师唐兴通专家人工智能大模型销售客户开发AI大数据挑战式销售顾问式销售专业销售向高层销售业绩增长创新

唐兴通 销售增长策略专家、数字销售实战导师 专注帮助企业构建面向AI数字时代新销售体系&#xff0c;擅长运用数字化工具重塑销售流程&#xff0c;提升销售业绩。作为《挑战式销售》译者&#xff0c;将全球顶尖销售理论大师马修狄克逊等理论导入中国销售业界。 核心专长&…