精讲23种设计模式-基于装饰模式~设计多级缓存框架

文章目录

          • 一、装饰模式
            • 1. 回顾多级缓存基本概念
            • 2. 装饰模式基本的概念
            • 3. 装饰模式应用场景
            • 4. 装饰者模式定义
            • 5. 基于Map手写Jvm内置缓存
          • 二、手写一级与二级缓存
            • 2.1. redis工具类
            • 2.2. 实体类
            • 2.3. 接口
            • 2.4. 数据库脚本
            • 2.5. 测试案例
            • 2.6. 测试效果分享
          • 三、设计多级缓存框架
            • 3.1. 缓存容器抽象
            • 3.2. 一级jvm缓存
            • 3.3. 二级缓存抽象接口
            • 3.4. 新增二级缓存
            • 3.5. Aop与自定义注解
            • 3.6. 实现二级缓存查询aop拦截
            • 3.7. 二级缓存外壳封装
            • 3.8. 缓存容器抽象
            • 3.9. 请求流程链路
            • 3.10. 开源项目

基于装饰模式设计多级缓存

一、装饰模式
1. 回顾多级缓存基本概念

在实际开发项目,为了减少数据库的访问压力,我们都会将数据缓存到内存中
比如:Redis(分布式缓存)、EHCHE(JVM内置缓存).
例如在早起中,项目比较小可能不会使用Redis做为缓存,使用JVM内置的缓存框架,
项目比较大的时候开始采用Redis分布式缓存框架,这时候需要设计一级与二级缓存。

2. 装饰模式基本的概念

不改变原有代码的基础之上,新增附加功能
在这里插入图片描述

3. 装饰模式应用场景

多级缓存设计、mybatis中一级与二级缓存、IO流

4. 装饰者模式定义

(1)抽象组件:定义一个抽象接口,来规范准备附加功能的类
(2)具体组件:将要被附加功能的类,实现抽象构件角色接口
(3)抽象装饰者:持有对具体构件角色的引用并定义与抽象构件角色一致的接口
(4)具体装饰:实现抽象装饰者角色,负责对具体构件添加额外功能。

5. 基于Map手写Jvm内置缓存
package com.gblfy.utils;import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** 基于Map手写Jvm内置缓存** @Author gblfy* @Date 2022-03-15 21:01**/
@Component
public class JvmMapCacheUtils {/*** 并发缓存容器*/private static Map<String, String> cacheList = new ConcurrentHashMap<String, String>();/*** 从本地缓存中根据key获取值** @param key 缓存key* @param t   返回对象类型* @param <T> 返回对象* @return*/public static <T> T getCache(String key, Class<T> t) {//    缓存存储对象String jsonValue = cacheList.get(key);return JSONObject.parseObject(jsonValue, t);}/*** 缓存数据到本地jvm中** @param key* @param val*/public static void putCache(String key, Object val) {String jsonValue = JSONObject.toJSONString(val);cacheList.put(key, jsonValue);}/*** 根据缓存key删除指定缓存数据** @param cacheKey*/public static void removeCacheCacheKey(String cacheKey) {cacheList.remove(cacheKey);}/*** 更新缓存数据** @param key* @param val*/public static void updateCacheByCacheKey(String key, Object val) {String jsonValue = JSONObject.toJSONString(val);cacheList.put(key, jsonValue);}}
二、手写一级与二级缓存
2.1. redis工具类
package com.gblfy.utils;import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.concurrent.TimeUnit;/*** redis工具类** @author gblfy* @date 2022-03-15*/
@Component
public class RedisUtils {@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 如果key存在的话返回fasle 不存在的话返回truepublic Boolean setNx(String key, String value, Long timeout) {Boolean setIfAbsent = stringRedisTemplate.opsForValue().setIfAbsent(key, value);if (timeout != null) {stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);}return setIfAbsent;}/*** 存放string类型** @param key     key* @param data    数据* @param timeout 超时间*/public void setString(String key, String data, Long timeout) {stringRedisTemplate.opsForValue().set(key, data);if (timeout != null) {stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);}}/*** 存放string类型** @param key  key* @param data 数据*/public void setString(String key, String data) {setString(key, data, null);}/*** 根据key查询string类型** @param key* @return*/public String getString(String key) {String value = stringRedisTemplate.opsForValue().get(key);return value;}public <T> T getEntity(String key, Class<T> t) {String json = getString(key);return JSONObject.parseObject(json, t);}public void putEntity(String key, Object object) {String json = JSONObject.toJSONString(object);setString(key, json);}/*** 根据对应的key删除key** @param key*/public boolean delKey(String key) {return stringRedisTemplate.delete(key);}public void setList(String key, List<String> listToken) {stringRedisTemplate.opsForList().leftPushAll(key, listToken);}public StringRedisTemplate getStringRedisTemplate() {return stringRedisTemplate;}
}
2.2. 实体类
package com.gblfy.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@Data
@TableName("gblfy_user")
public class UserEntity {// 主键@TableId(value = "user_id", type = IdType.ASSIGN_ID)private Integer userId;//用户名称@TableField("name")private String name;
}
2.3. 接口
package com.gblfy.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gblfy.entity.UserEntity;
import org.apache.ibatis.annotations.Select;public interface UserMapper extends BaseMapper<UserEntity> {
}
2.4. 数据库脚本
drop database  IF EXISTS `design_pattern`;
create database `design_pattern`;
use `design_pattern`;SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for gblfy_strategy
-- ----------------------------
DROP TABLE IF EXISTS `gblfy_user`;
CREATE TABLE `gblfy_user` (`user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户ID',`name` varchar(32) NOT NULL COMMENT '用户名称',PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';INSERT INTO `gblfy_user` VALUES (1, '雨昕');
2.5. 测试案例
package com.gblfy.controller;import com.gblfy.entity.UserEntity;
import com.gblfy.mapper.UserMapper;
import com.gblfy.utils.JvmMapCacheUtils;
import com.gblfy.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** 使用装饰模式~查询用户数据** @Author gblfy* @Date 2022-03-15 21:12**/
@Slf4j
@RestController
public class UserController {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RedisUtils redisUtils;@GetMapping("/getUser")public UserEntity getUser(Integer userId) {//一级缓存和二级缓存//方法名+参数类型+参数String key = "getUser(Integer)" + userId;//先查询二级缓存UserEntity redisUser = redisUtils.getEntity(key, UserEntity.class);if (redisUser != null) {return redisUser;}//先查询我们的一级缓存(jvm内置缓存)UserEntity jvmUser = JvmMapCacheUtils.getCache(key, UserEntity.class);if (jvmUser != null) {//当一级缓存不为空时,将内容添加到二级缓存redia中,减少一级缓存的查询压力redisUtils.putEntity(key, jvmUser);return jvmUser;}//查询我们的dbUserEntity dbUser = userMapper.selectById(userId);if (dbUser == null) {return null;}//将db查询的内容添加到一级缓存中,减少数据库压力JvmMapCacheUtils.putCache(key, dbUser);return dbUser;}
}
2.6. 测试效果分享

当第一次查询用户数据时流程如下:
先判断redis中是否存在,如果不存在,查询jvm缓存中是否存在
当 jvm缓存中不存在时,查询数据库,再将查询出来的数据添加到jvm缓存中

当第二次查询用户数据时流程如下:
先判断redis中是否存在,如果不存在,查询jvm缓存中是否存在
当 jvm缓存中存在时,先将查询出来的数据添加到redis缓存中,再返回响应缓存数据

当第三次查询用户数据时流程如下:
先判断redis中是否存在,如果存在,直接返回缓存数据

三、设计多级缓存框架
3.1. 缓存容器抽象
package com.gblfy.decoration;import org.aspectj.lang.ProceedingJoinPoint;/*** 缓存容器抽象** @Author gblfy* @Date 2022-03-15 21:42**/
public interface ComponentCache {/*** 根据key查询缓存数据** @param key       缓存的key* @param t         传入对象参数类型* @param joinPoint 目标方法内容* @param <T>       返回的对象类型* @return*/<T> T getCacheEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint);
}
3.2. 一级jvm缓存
package com.gblfy.decoration.impl;import com.gblfy.decoration.ComponentCache;
import com.gblfy.utils.JvmMapCacheUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;/*** 一级缓存查询处理类** @Author gblfy* @Date 2022-03-15 21:45**/
@Component
public class JvmComponentCache implements ComponentCache {// @Autowired// private UserMapper userMapper;@Overridepublic <T> T getCacheEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint) {//先查询我们的一级缓存(jvm内置缓存)T jvmUser = JvmMapCacheUtils.getCache(key, t);if (jvmUser != null) {return (T) jvmUser;}//查询我们的db// UserEntity dbUser = userMapper.selectById("1");// if (dbUser == null) {//     return null;// }try {/*** 当一级缓存不存在时,查询我们的db,相当于userMapper.selectById(userId)这一行代码* 1.通过aop直接获取目标对象的方法* 解析:* 目的:执行joinPoint.proceed();这一行就相当于执行目标方法,为了做成抽象通用的,* 方案:采用aop来实现仅此而已*/Object resultDb = joinPoint.proceed();//将db查询的内容添加到一级缓存中,减少数据库压力JvmMapCacheUtils.putCache(key, resultDb);return (T) resultDb;} catch (Throwable e) {e.printStackTrace();return null;}}
}
3.3. 二级缓存抽象接口
package com.gblfy.decoration;/*** 二级缓存抽象接口** @author gblfy* @date 2022-03-16*/
public interface AbstractDecorate extends ComponentCache {
}
3.4. 新增二级缓存
package com.gblfy.decoration.impl;import com.gblfy.decoration.AbstractDecorate;
import com.gblfy.utils.RedisUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** 二级缓存查询处理类** @Author gblfy* @Date 2022-03-15 21:50**/
@Component
public class RedistDecorate extends JvmComponentCache implements AbstractDecorate {@Autowiredprivate RedisUtils redisUtils;// @Autowired// private JvmComponentCache jvmComponentCache;@Overridepublic <T> T getCacheEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint) {//先查询二级缓存T tRedis = redisUtils.getEntity(key, t);if (tRedis != null) {return (T) tRedis;}//先查询我们的一级缓存(jvm内置缓存)T tJvm = super.getCacheEntity(key, t, joinPoint);//如果 extends JvmComponentCache的话可以写成上面super.getCacheEntity(key)这种,前提是装饰类不能new// UserEntity jvmUser = jvmComponentCache.getCacheEntity(key);if (tJvm == null) {return null;}//当一级缓存不为空时,将内容添加到二级缓存redia中,减少一级缓存的查询压力redisUtils.putEntity(key, tJvm);return (T) tJvm;}
}
3.5. Aop与自定义注解
package com.gblfy.annotation;import java.lang.annotation.*;/*** 二级缓存查询aop拦截** @author gblfy* @date 2022-03-15*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExtGblfyCache {
}
3.6. 实现二级缓存查询aop拦截
package com.gblfy.aop;import com.gblfy.decoration.GblfyCache;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.Arrays;/*** 实现二级缓存查询aop拦截* 自定义ExtGblfyCache注解** @author gblfy* @date 2022-03-15*/
@Aspect
@Component
@Slf4j
public class ExtAsyncAop {@Autowiredprivate GblfyCache gblfyCache;@Around(value = "@annotation(com.gblfy.annotation.ExtGblfyCache)")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {Signature signature = joinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;//获取目标方法Method targetMethod = methodSignature.getMethod();//缓存key拼接(方法名+参数类型+参数值)String cacheKey = targetMethod.getName() + "," + Arrays.toString(targetMethod.getParameterTypes());log.info(">>cacheKey:" + cacheKey);// 开始先查询二级缓存是否存在return gblfyCache.getCacheEntity(cacheKey, targetMethod.getReturnType(), joinPoint);//    这里的泛型T等于方法的返回结果类型简言之targetMethod.getReturnType()}
}
3.7. 二级缓存外壳封装
package com.gblfy.decoration;import com.gblfy.decoration.impl.RedistDecorate;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** 二级缓存外壳封装** @Author gblfy* @Date 2022-03-15 22:01**/
@Component
public class GblfyCache {@Autowiredprivate RedistDecorate redistDecorate;public <T> T getCacheEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint) {return redistDecorate.getCacheEntity(key, t, joinPoint);}
}
3.8. 缓存容器抽象
package com.gblfy.controller;import com.gblfy.annotation.ExtGblfyCache;
import com.gblfy.entity.UserEntity;
import com.gblfy.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** 使用装饰模式~查询用户数据** @Author gblfy* @Date 2022-03-15 21:12**/
@Slf4j
@RestController
public class UserController {@Autowiredprivate UserMapper userMapper;@GetMapping("/getUser")@ExtGblfyCachepublic UserEntity getUser(Integer userId) {return userMapper.selectById(userId);}}
3.9. 请求流程链路

当我们访问http://localhost:8080/getUser?userId=1方法时,由于在该方法上有@ExtGblfyCache注解修饰,因此,会被aop拦截。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


当地二次查询时,就会只查询redis,查询到后直接返回

3.10. 开源项目

https://gitee.com/gblfy/design-pattern/tree/decoration-mode/

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

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

相关文章

阿里云EDAS 3.0重磅发布,无侵入构建云原生应用

发布会传送门 进入直播间还有好礼等你拿&#xff01; EDAS产品免费试用&#xff1a;https://www.aliyun.com/activity/middleware/edaspromotiononmay 首届云原生编程挑战赛正式开战&#xff01;立即报名瓜分330000现金奖&#xff1a;https://tianchi.aliyun.com/specials/p…

二维数组的偏移量

数组的偏移量&#xff1a; 数组空间起始位置的偏移值。 公式&#xff1a; 例题&#xff1a; 结合图片分析例题和公式&#xff1a;

Akamai “三驾马车”,如何应对疫情后新场景形态下的新考验?

2020年10月14日&#xff0c;CDN行业领头羊、负责提供安全数字化体验的智能边缘平台Akamai&#xff08;阿卡迈技术&#xff09;发布了其边缘计算、媒体交付和安全方面的产品组合的多项更新。其中在Akamai智能边缘&#xff08;Akamai Intelligent Edge&#xff09;、媒体交付、应…

如何使用MaxCompute Spark读写阿里云Hbase

背景 Spark on MaxCompute可以访问位于阿里云VPC内的实例&#xff08;例如ECS、HBase、RDS&#xff09;,默认MaxCompute底层网络和外网是隔离的&#xff0c;Spark on MaxCompute提供了一种方案通过配置spark.hadoop.odps.cupid.vpc.domain.list来访问阿里云的vpc网络环境的Hba…

elementui更改el-table表头背景颜色和字体颜色

博主在使用elementui中的el-table时感觉默认表格样式实在过于简洁&#xff0c;尤其表头与表格内容之间区别较小&#xff0c;不利于辨认&#xff0c;降低了用户体验。如图所示&#xff1a; 于是&#xff0c;博主尝试更改一下表头的背景颜色和字体颜色&#xff0c;方法如下&…

idea 提升幸福感 常用设置(重装机配置)

1.常用快捷键 alt 7 展示类的方法 CtrlH 查看当前所选类的继承关系 CtrlShift上下键 上下移动整行 2.自动导包&#xff1a; 3.自动创建 serialVersionUID IDEA 自动给实现了 Serializable 接口的类创建 serialVersionUID 4.类与方法注释快捷键设置 方法注释模板设置 类与方…

ClickHouse内核分析-MergeTree的Merge和Mutation机制

注&#xff1a;以下分析基于开源 v19.15.2.2-stable 版本进行 引言 ClickHouse内核分析系列文章&#xff0c;继上一篇文章 MergeTree查询链路 之后&#xff0c;这次我将为大家介绍MergeTree存储引擎的异步Merge和Mutation机制。建议读者先补充上一篇文章的基础知识&#xff0…

el-table中奇偶行背景色显示不同的颜色

默认样式 深色主题 border ref"singleTable" highlight-current-row current-change"handleCurrentChange" :row-class-name"tableRowClassName" :header-cell-style"{background:#004d8c,color:#FFFFFF}"事件方法 //奇偶行背景色不…

阿里云专属数据库,重新定义云数据库新形态

阿里云数据库专属集群专属链接 云专属数据库&#xff0c;重新定义云数据库新形态 数据库是一个有着超过40年历史的悠久行业&#xff0c;前期一直被传统的如Oracle等少数几家厂商把持。云计算的先行者AWS在2009年率先推出RDS服务&#xff08;Relational Database Service &…

软考零散知识点

网络命令 多态 强制多态&#xff1a;数字类型运算的自动拆装箱 过载多态&#xff1a;子类重写父类的方法 参数多态&#xff1a;方法的重载 包含多态&#xff1a;父类的引用指向子类的对象 主存和cache映射 RAID RAID RAID0&#xff1a;无冗余备份&#xff0c;带化。每条数据…

ServiceMesh最火项目:Istio架构解析

Istio 是一个开源的服务网格&#xff0c;可为分布式微服务架构提供所需的基础运行和管理要素。随着各组织越来越多地采用云平台&#xff0c;开发者必须使用微服务设计架构以实现可移植性&#xff0c;而运维人员必须管理包含混合云部署和多云部署的大型分布式应用。Istio 采用一…

docker-compose 实战案例

文章目录一、Compose入门案例1. 依赖2. 实体类3. mapper接口4. 启动类5. yml配置6. 测试案例7. 打包二、制作 DockerFile和docker-compose.yml2.1. 制作 DockerFile2.2. docker-compose.yml三、打包部署3.1. 资料上传3.2. 启动docker-compose3.3. 创建表3.4. 接口测试3.5. 数据…

F5打造“感知可控,随需而变的应用”  助力企业实现非凡数字体验

2020年12月16日&#xff0c;F5举办线上发布会&#xff0c;介绍其全新理念—“感知可控&#xff0c;随需而变的应用”(Adaptive Applications)&#xff0c;以及相应的创新性整体解决方案。在当前数字化转型加速的背景下&#xff0c;F5致力于为企业打造感知可控、随需应变的应用&…

软考 - 排序算法

文章目录1.总览1.待操作数组2.直接插入排序&#xff08;O(n2)&#xff09;3.希尔排序4.直接选择排序5.堆排序5.1.堆的分类5.2.原理&#xff1a;5.3. 堆排序方法&#xff1a;6.冒泡排序7.快速排序8.归并排序9.基数排序1.总览 1.待操作数组 private static int[] ori {30, 70, …

“数据湖”:概念、特征、架构与案例

写在前面&#xff1a; 最近&#xff0c;数据湖的概念非常热&#xff0c;许多前线的同学都在讨论数据湖应该怎么建&#xff1f;阿里云有没有成熟的数据湖解决方案&#xff1f;阿里云的数据湖解决方案到底有没有实际落地的案例&#xff1f;怎么理解数据湖&#xff1f;数据湖和大数…

DockerFile 入门到精通

文章目录一、DockerFile快速入门1. DockerFile 解析2. DockerFile编写规范3. DockerFile指令二、构建自己centos镜像2.1. 制作Dockerfile2.2. 构建镜像2.3. 运行容器一、DockerFile快速入门 1. DockerFile 解析 一个镜像文件到底是如何创建&#xff1f; dockerfile 描述出镜…

案例解析|广东自由流收费稽核方案,AI稽核新模式

随着取消省界收费站工程落成&#xff0c;我国逐步迈进全国高速公路“一张网”运行感知新时代。借助交通强国和“撤站”政策&#xff0c;2019年12月&#xff0c;广东联合电服和阿里云共同宣布&#xff0c;全国首个高速不停车收费AI稽核项目正式落地广东&#xff0c;在业内率先使…

赠书 | 读懂 x86 架构 CPU 虚拟化,看这文就够了

作者 | 王柏生、谢广军导读&#xff1a;本文摘自于王柏生、谢广军撰写的《深度探索Linux系统虚拟化&#xff1a;原理与实现》一书&#xff0c;介绍了CPU虚拟化的基本概念&#xff0c;探讨了x86架构在虚拟化时面临的障碍&#xff0c;以及为支持CPU虚拟化&#xff0c;Intel在硬件…

nacos 持久化 mysql(windows/linux环境)

文章目录1. 下载nacos-server2. 修改配置3. 创建数据库4. 初始化数据库脚本5. 启动nacos1. 下载nacos-server nacos-server-1.4.3.zip https://github.91chi.fun//https://github.com//alibaba/nacos/releases/download/1.4.3/nacos-server-1.4.3.zip解压 略 2. 修改配置 c…

HSF服务注册失败,项目启动后,EDAS列表无法发现注册的服务

背景&#xff1a; 本地使用edas轻量配置中心进行开发联调。 异常现象&#xff1a; 1.redis和edas已经启动正常&#xff0c;本地http://localhost:8080可以打开&#xff0c;但是配置列表和服务列表为空。 2.项目可以正常启动&#xff0c;但是postman调用时&#xff0c;发生hs…