精讲23种设计模式-基于装饰模式~设计多级缓存框架
文章目錄
- 一、裝飾模式
- 1. 回顧多級(jí)緩存基本概念
- 2. 裝飾模式基本的概念
- 3. 裝飾模式應(yīng)用場(chǎng)景
- 4. 裝飾者模式定義
- 5. 基于Map手寫Jvm內(nèi)置緩存
- 二、手寫一級(jí)與二級(jí)緩存
- 2.1. redis工具類
- 2.2. 實(shí)體類
- 2.3. 接口
- 2.4. 數(shù)據(jù)庫腳本
- 2.5. 測(cè)試案例
- 2.6. 測(cè)試效果分享
- 三、設(shè)計(jì)多級(jí)緩存框架
- 3.1. 緩存容器抽象
- 3.2. 一級(jí)jvm緩存
- 3.3. 二級(jí)緩存抽象接口
- 3.4. 新增二級(jí)緩存
- 3.5. Aop與自定義注解
- 3.6. 實(shí)現(xiàn)二級(jí)緩存查詢aop攔截
- 3.7. 二級(jí)緩存外殼封裝
- 3.8. 緩存容器抽象
- 3.9. 請(qǐng)求流程鏈路
- 3.10. 開源項(xiàng)目
基于裝飾模式設(shè)計(jì)多級(jí)緩存
一、裝飾模式
1. 回顧多級(jí)緩存基本概念
在實(shí)際開發(fā)項(xiàng)目,為了減少數(shù)據(jù)庫的訪問壓力,我們都會(huì)將數(shù)據(jù)緩存到內(nèi)存中
比如:Redis(分布式緩存)、EHCHE(JVM內(nèi)置緩存).
例如在早起中,項(xiàng)目比較小可能不會(huì)使用Redis做為緩存,使用JVM內(nèi)置的緩存框架,
項(xiàng)目比較大的時(shí)候開始采用Redis分布式緩存框架,這時(shí)候需要設(shè)計(jì)一級(jí)與二級(jí)緩存。
2. 裝飾模式基本的概念
不改變?cè)写a的基礎(chǔ)之上,新增附加功能
3. 裝飾模式應(yīng)用場(chǎng)景
多級(jí)緩存設(shè)計(jì)、mybatis中一級(jí)與二級(jí)緩存、IO流
4. 裝飾者模式定義
(1)抽象組件:定義一個(gè)抽象接口,來規(guī)范準(zhǔn)備附加功能的類
(2)具體組件:將要被附加功能的類,實(shí)現(xiàn)抽象構(gòu)件角色接口
(3)抽象裝飾者:持有對(duì)具體構(gòu)件角色的引用并定義與抽象構(gòu)件角色一致的接口
(4)具體裝飾:實(shí)現(xiàn)抽象裝飾者角色,負(fù)責(zé)對(duì)具體構(gòu)件添加額外功能。
5. 基于Map手寫Jvm內(nèi)置緩存
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內(nèi)置緩存** @Author gblfy* @Date 2022-03-15 21:01**/ @Component public class JvmMapCacheUtils {/*** 并發(fā)緩存容器*/private static Map<String, String> cacheList = new ConcurrentHashMap<String, String>();/*** 從本地緩存中根據(jù)key獲取值** @param key 緩存key* @param t 返回對(duì)象類型* @param <T> 返回對(duì)象* @return*/public static <T> T getCache(String key, Class<T> t) {// 緩存存儲(chǔ)對(duì)象String jsonValue = cacheList.get(key);return JSONObject.parseObject(jsonValue, t);}/*** 緩存數(shù)據(jù)到本地jvm中** @param key* @param val*/public static void putCache(String key, Object val) {String jsonValue = JSONObject.toJSONString(val);cacheList.put(key, jsonValue);}/*** 根據(jù)緩存key刪除指定緩存數(shù)據(jù)** @param cacheKey*/public static void removeCacheCacheKey(String cacheKey) {cacheList.remove(cacheKey);}/*** 更新緩存數(shù)據(jù)** @param key* @param val*/public static void updateCacheByCacheKey(String key, Object val) {String jsonValue = JSONObject.toJSONString(val);cacheList.put(key, jsonValue);}}二、手寫一級(jí)與二級(jí)緩存
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 數(shù)據(jù)* @param timeout 超時(shí)間*/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 數(shù)據(jù)*/public void setString(String key, String data) {setString(key, data, null);}/*** 根據(jù)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);}/*** 根據(jù)對(duì)應(yīng)的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. 實(shí)體類
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. 數(shù)據(jù)庫腳本
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. 測(cè)試案例
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;/*** 使用裝飾模式~查詢用戶數(shù)據(jù)** @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) {//一級(jí)緩存和二級(jí)緩存//方法名+參數(shù)類型+參數(shù)String key = "getUser(Integer)" + userId;//先查詢二級(jí)緩存UserEntity redisUser = redisUtils.getEntity(key, UserEntity.class);if (redisUser != null) {return redisUser;}//先查詢我們的一級(jí)緩存(jvm內(nèi)置緩存)UserEntity jvmUser = JvmMapCacheUtils.getCache(key, UserEntity.class);if (jvmUser != null) {//當(dāng)一級(jí)緩存不為空時(shí),將內(nèi)容添加到二級(jí)緩存redia中,減少一級(jí)緩存的查詢壓力redisUtils.putEntity(key, jvmUser);return jvmUser;}//查詢我們的dbUserEntity dbUser = userMapper.selectById(userId);if (dbUser == null) {return null;}//將db查詢的內(nèi)容添加到一級(jí)緩存中,減少數(shù)據(jù)庫壓力JvmMapCacheUtils.putCache(key, dbUser);return dbUser;} }2.6. 測(cè)試效果分享
當(dāng)?shù)谝淮尾樵冇脩魯?shù)據(jù)時(shí)流程如下:
先判斷redis中是否存在,如果不存在,查詢jvm緩存中是否存在
當(dāng) jvm緩存中不存在時(shí),查詢數(shù)據(jù)庫,再將查詢出來的數(shù)據(jù)添加到j(luò)vm緩存中
當(dāng)?shù)诙尾樵冇脩魯?shù)據(jù)時(shí)流程如下:
先判斷redis中是否存在,如果不存在,查詢jvm緩存中是否存在
當(dāng) jvm緩存中存在時(shí),先將查詢出來的數(shù)據(jù)添加到redis緩存中,再返回響應(yīng)緩存數(shù)據(jù)
當(dāng)?shù)谌尾樵冇脩魯?shù)據(jù)時(shí)流程如下:
先判斷redis中是否存在,如果存在,直接返回緩存數(shù)據(jù)
三、設(shè)計(jì)多級(jí)緩存框架
3.1. 緩存容器抽象
package com.gblfy.decoration;import org.aspectj.lang.ProceedingJoinPoint;/*** 緩存容器抽象** @Author gblfy* @Date 2022-03-15 21:42**/ public interface ComponentCache {/*** 根據(jù)key查詢緩存數(shù)據(jù)** @param key 緩存的key* @param t 傳入對(duì)象參數(shù)類型* @param joinPoint 目標(biāo)方法內(nèi)容* @param <T> 返回的對(duì)象類型* @return*/<T> T getCacheEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint); }3.2. 一級(jí)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;/*** 一級(jí)緩存查詢處理類** @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) {//先查詢我們的一級(jí)緩存(jvm內(nèi)置緩存)T jvmUser = JvmMapCacheUtils.getCache(key, t);if (jvmUser != null) {return (T) jvmUser;}//查詢我們的db// UserEntity dbUser = userMapper.selectById("1");// if (dbUser == null) {// return null;// }try {/*** 當(dāng)一級(jí)緩存不存在時(shí),查詢我們的db,相當(dāng)于userMapper.selectById(userId)這一行代碼* 1.通過aop直接獲取目標(biāo)對(duì)象的方法* 解析:* 目的:執(zhí)行joinPoint.proceed();這一行就相當(dāng)于執(zhí)行目標(biāo)方法,為了做成抽象通用的,* 方案:采用aop來實(shí)現(xiàn)僅此而已*/Object resultDb = joinPoint.proceed();//將db查詢的內(nèi)容添加到一級(jí)緩存中,減少數(shù)據(jù)庫壓力JvmMapCacheUtils.putCache(key, resultDb);return (T) resultDb;} catch (Throwable e) {e.printStackTrace();return null;}} }3.3. 二級(jí)緩存抽象接口
package com.gblfy.decoration;/*** 二級(jí)緩存抽象接口** @author gblfy* @date 2022-03-16*/ public interface AbstractDecorate extends ComponentCache { }3.4. 新增二級(jí)緩存
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;/*** 二級(jí)緩存查詢處理類** @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) {//先查詢二級(jí)緩存T tRedis = redisUtils.getEntity(key, t);if (tRedis != null) {return (T) tRedis;}//先查詢我們的一級(jí)緩存(jvm內(nèi)置緩存)T tJvm = super.getCacheEntity(key, t, joinPoint);//如果 extends JvmComponentCache的話可以寫成上面super.getCacheEntity(key)這種,前提是裝飾類不能new// UserEntity jvmUser = jvmComponentCache.getCacheEntity(key);if (tJvm == null) {return null;}//當(dāng)一級(jí)緩存不為空時(shí),將內(nèi)容添加到二級(jí)緩存redia中,減少一級(jí)緩存的查詢壓力redisUtils.putEntity(key, tJvm);return (T) tJvm;} }3.5. Aop與自定義注解
package com.gblfy.annotation;import java.lang.annotation.*;/*** 二級(jí)緩存查詢aop攔截** @author gblfy* @date 2022-03-15*/ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ExtGblfyCache { }3.6. 實(shí)現(xiàn)二級(jí)緩存查詢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;/*** 實(shí)現(xiàn)二級(jí)緩存查詢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;//獲取目標(biāo)方法Method targetMethod = methodSignature.getMethod();//緩存key拼接(方法名+參數(shù)類型+參數(shù)值)String cacheKey = targetMethod.getName() + "," + Arrays.toString(targetMethod.getParameterTypes());log.info(">>cacheKey:" + cacheKey);// 開始先查詢二級(jí)緩存是否存在return gblfyCache.getCacheEntity(cacheKey, targetMethod.getReturnType(), joinPoint);// 這里的泛型T等于方法的返回結(jié)果類型簡(jiǎn)言之targetMethod.getReturnType()} }3.7. 二級(jí)緩存外殼封裝
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;/*** 二級(jí)緩存外殼封裝** @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;/*** 使用裝飾模式~查詢用戶數(shù)據(jù)** @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. 請(qǐng)求流程鏈路
當(dāng)我們?cè)L問http://localhost:8080/getUser?userId=1方法時(shí),由于在該方法上有@ExtGblfyCache注解修飾,因此,會(huì)被aop攔截。
當(dāng)?shù)囟尾樵儠r(shí),就會(huì)只查詢r(jià)edis,查詢到后直接返回
3.10. 開源項(xiàng)目
https://gitee.com/gblfy/design-pattern/tree/decoration-mode/
總結(jié)
以上是生活随笔為你收集整理的精讲23种设计模式-基于装饰模式~设计多级缓存框架的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mysql 启动报错解析:Startin
- 下一篇: (进阶篇)Redis6.2.0 集群 主