一、装饰模式
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. 开源项目
基于装饰模式设计多级缓存
在实际开发项目,为了减少数据库的访问压力,我们都会将数据缓存到内存中
比如:Redis(分布式缓存)、EHCHE(JVM内置缓存).
例如在早起中,项目比较小可能不会使用Redis做为缓存,使用JVM内置的缓存框架,
项目比较大的时候开始采用Redis分布式缓存框架,这时候需要设计一级与二级缓存。
不改变原有代码的基础之上,新增附加功能
多级缓存设计、mybatis中一级与二级缓存、IO流
4. 装饰者模式定义(1)抽象组件:定义一个抽象接口,来规范准备附加功能的类
(2)具体组件:将要被附加功能的类,实现抽象构件角色接口
(3)抽象装饰者:持有对具体构件角色的引用并定义与抽象构件角色一致的接口
(4)具体装饰:实现抽象装饰者角色,负责对具体构件添加额外功能。
package com.gblfy.utils;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class JvmMapCacheUtils {
private static Map cacheList = new ConcurrentHashMap();
public static T getCache(String key, Class t) {
// 缓存存储对象
String jsonValue = cacheList.get(key);
return JSONObject.parseObject(jsonValue, t);
}
public static void putCache(String key, Object val) {
String jsonValue = JSONObject.toJSONString(val);
cacheList.put(key, jsonValue);
}
public static void removeCacheCacheKey(String cacheKey) {
cacheList.remove(cacheKey);
}
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;
@Component
public class RedisUtils {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 如果key存在的话返回fasle 不存在的话返回true
public 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;
}
public void setString(String key, String data, Long timeout) {
stringRedisTemplate.opsForValue().set(key, data);
if (timeout != null) {
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
}
public void setString(String key, String data) {
setString(key, data, null);
}
public String getString(String key) {
String value = stringRedisTemplate.opsForValue().get(key);
return value;
}
public T getEntity(String key, Class 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);
}
public boolean delKey(String key) {
return stringRedisTemplate.delete(key);
}
public void setList(String key, List 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 baseMapper2.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;
@Slf4j
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@Autowired
private 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;
}
//查询我们的db
UserEntity 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中是否存在,如果存在,直接返回缓存数据
package com.gblfy.decoration;
import org.aspectj.lang.ProceedingJoinPoint;
public interface ComponentCache {
T getCacheEntity(String key, Class 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;
@Component
public class JvmComponentCache implements ComponentCache {
// @Autowired
// private UserMapper userMapper;
@Override
public T getCacheEntity(String key, Class 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 {
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;
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;
@Component
public class RedistDecorate extends JvmComponentCache implements AbstractDecorate {
@Autowired
private RedisUtils redisUtils;
// @Autowired
// private JvmComponentCache jvmComponentCache;
@Override
public T getCacheEntity(String key, Class 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.*;
@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;
@Aspect
@Component
@Slf4j
public class ExtAsyncAop {
@Autowired
private 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;
@Component
public class GblfyCache {
@Autowired
private RedistDecorate redistDecorate;
public T getCacheEntity(String key, Class 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;
@Slf4j
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/getUser")
@ExtGblfyCache
public 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/



