文章目录

  • 一、装饰模式
    • 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/

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

  1. 精讲23种设计模式-基于观察者模式~设计异步多渠道群发框架

    文章目录 一.观察者模式 1. 观察者模式基本概念 2. 观察者模式的应用场景 3. 观察者模式的类图 二.设计异步多渠道群发框架 2.1. 定义消息观察者抽象接口 2.2. 创建观察者 2.3. 主 ...

  2. 精讲23种设计模式-基于责任链模式~构建企业级风控系统

    文章目录 一.责任链 1. 责任链基本概念 2. 定义 3. 关键要点 4. 责任链模式优缺点 5. 责任链模式类结构图 6. 网关权限控制责任链模式 二.构建企业级风控系统 2.1. 定义公共抽象任 ...

  3. 精讲23种设计模式-策略模式~聚合短信服务和聚合支付服务

    文章目录 一.设计模式 1. 为什么需要使用设计模式 2. 设计模式的分类 3. 什么是策略模式 4. 为什么叫做策略模式 5. 策略模式优缺点 6. 策略模式应用场景 7. Spring框架中使用的 ...

  4. 李建忠讲23种设计模式笔记-上

    前两天在B站看了李建忠老师讲23种设计模式的系列视频,收获颇丰.设计模式是前人的智慧和经验的总结,需要我们反复地学习.在实践中体会其蕴含的设计原则和设计思想.李老师按照封装变化将23种设计模式分成组件 ...

  5. 在王者荣耀角度下分析面向对象程序设计B中23种设计模式之装饰模式

    · 装饰模式在王者荣耀中的应用 · 一.简述 在王者荣耀这款游戏中,英雄在战场上高伤害.高爆发.高移速等是所有玩家共同追求的,那么这些所谓的伤害.移速.穿透力等英雄属性我们可以通过在局外对英雄附带皮肤 ...

  6. Java 23种设计模式(1.设计模式概念和UML)

    1. 设计模式概念 软件设计模式(Software Design Pattern),又称设计模式 是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结 1.1 设计模式学习必要性理由 设 ...

  7. 23种设计模式之装饰模式

    装饰模式的定义 定义: 动态的给一个对象添加一些额外的职责. 就增加功能来说, 装饰模式相比生成子类更为灵活. 通俗的说, 就是对一个类或方法进行包装 装饰模式的通用类图: 类图中的四个角色说明如下: ...

  8. 终于有人将23种设计模式与七大设计原则整理明白了!!!

  9. Java内功修炼 - 23种设计模式

    一个设计模式的出现一定有它特殊的价值 前段时间二刷Java设计模式 对于设计模式来说,这个东西是始终不变的,所以我把这方面知识归纳为Java内功. 一个技术超牛的程序员,也就和修仙类小说男主角一样.不 ...

最新文章

  1. Udacity机器人软件工程师课程笔记(二十) - 感知 - 校准,过滤, 分段, RANSAC
  2. 报错处理: syntax error near unexpected token `$‘\r‘‘ (linux中的^M)
  3. socket通信流程图
  4. cmake构建工具 初步01
  5. 信息检索 python_python-工程数据结构/信息检索和存储
  6. Python super() 函数的用法及实例
  7. kafka发送消息至指定分区
  8. 最适合画画的平板电脑_平板电脑性价比排行 2020年最值得入手的平板
  9. demo.php url,return_url.php
  10. 点线面的特点_黑白装饰画——点线面 设计入门必备
  11. $(document).ready()方法和window.onload()方法
  12. 谷歌地图TMS地图服务地址收集,测试可用
  13. Zynq-Linux移植学习笔记之57-国产ZYNQ PL挂载兆易创新GD25S512 flash
  14. 用C#实现汉字转化为拼音
  15. 网络安全现状及防范措施
  16. Hadoop综合大作业+补交平时作业
  17. android用户和AID
  18. fiddler连接手机
  19. 如何申请Office365试用账号
  20. java哪些类重写equals方法_Java自定义类中重写equals方法

热门文章

  1. 图灵,蔡汀,达尔文:计算中的上帝
  2. 怎样才能找到一份AI领域的好工作?
  3. 推荐系统--矩阵分解(2)
  4. android百度多渠道打包,Android多渠道打包方案的实践与优化
  5. java Trie实现英文单词查找树 搜索自动提示
  6. RMQ算法,求区间最值
  7. 阿里云混合云Apsara Stack 2.0发布 加速政企数智创新
  8. 2020双11,阿里巴巴集团数万数据库系统全面上云揭秘
  9. 完美日记:保障电商大促活动平稳运行
  10. 阿里小二的日常工作要被TA们“接管”了!