Mybatis深入源码分析之基于【装饰设计模式】纯手写实现多级缓
前言:设计模式源于生活
什么是装饰模式
在不改变原有对象的基础上附加功能,相比生成子类更灵活。
装饰者模式应用场景
Mybatis缓存,过滤器,网关控制,P2P分控审批
装饰者模式定义
(1)抽象组件:定义一个抽象接口,来规范准备附加功能的类
(2)具体组件:将要被附加功能的类,实现抽象构件角色接口
(3)抽象装饰者:持有对具体构件角色的引用并定义与抽象构件角色一致的接口
(4)具体装饰:实现抽象装饰者角色,负责对具体构件添加额外功能。
装饰模式和代理模式区别?
代理模式:在方法之前和之后实现处理,在方法上实现增强,隐藏真实方法的真实性,保证安全。
装饰模式:不改变原有的功能,实现增强,不断新增很多装饰。
多级缓存框架的设计
一般场景如下:
1.在很早之前框架只有一级缓存,数据缓存在jvm内存中,首先去查询内存是否有数据,如果没有数据则查询db,然后再将数据添加到jvm内存
2.第二次查询数据,查询jvm,jvm有数据,则直接返回view,否则又进行查询db,但是这样也会有个缺点,就是数据过多,可能会造成jvm内存溢出
3.后来有了redis或其他的缓存框架,就方便实现了二级缓存,或者三级甚至更高的缓存,但是同样也会缺点,就是会造成缓存穿透
首先在实现多级缓存框架之前,我先大概讲解一下我的实现的思路原理
1.首先我会定义装饰抽象骨架,用于定义一个基础的动作组件
2.定义一个具体组件,用于实现一级缓存
3.定义一个装饰类,用于后期扩展骨架
4.定义一个装饰具体类,用于实现二级缓存
5.定义一个注解,通过AOP来控制缓存和业务代码,以便代码的简洁度和可读性
多级缓存框架实现开始
首先展示一波,我的整体项目结构
先将工具类贴到前面,怕代码大家阅读代码看混乱
jvm工具类
public class JvmMapCacheUtils {
private static Map<String, String> caches = new ConcurrentHashMap<>();
/**
* 从内存中获取缓存
*
* @param key
* @param t
* @param <T>
* @return
*/
public static <T> T getEntity(String key, Class<T> t) {
String json = caches.get(key);
T t1 = JSONObject.parseObject(json, t);
return t1;
}
/**
* 添加缓存至内存中
*
* @param key
* @param value
*/
public static void putCache(String key, Object value) {
String jsonString = JSONObject.toJSONString(value);
caches.put(key, jsonString);
}
}
redis工具类
@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;
}
/**
* 存放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;
}
}
定义一个抽象组件,用于构建我们的骨架
public interface ComponentCache {
/**
* 根据key查询缓存数据
*
* @param key key查询缓存
* @param clz 动态返回对象
* @param joinPoint 目标对象方法
* @param <T> 返回对象
* @return
*/
<T> T getCacheEntity(String key, Class<T> clz, ProceedingJoinPoint joinPoint);
}
定义一个具体组件,实现一级缓存
这里的思路是,通过key查询jvm内存是否有数据,有数据直接返回结果,没有数据,则通过AOP执行目标对象方法,查询数据库,将结果再插入到jvm内存中
@Slf4j
@Component
public class JvmComponentCache implements ComponentCache {
/**
* 查询jvm一级缓存
*
* @param key key查询缓存
* @param clz 动态返回对象
* @param joinPoint 目标对象方法
* @param <T>
* @return
*/
@Override
public <T> T getCacheEntity(String key, Class<T> clz, ProceedingJoinPoint joinPoint) {
//一级缓存
T t = JvmMapCacheUtils.getEntity(key, clz);
if (t != null) {
log.info("查询一级缓存,{}", t);
return t;
}
try {
log.info("查询db");
//这个地方执行目标方法
Object dbResult = joinPoint.proceed();
JvmMapCacheUtils.putCache(key, dbResult);
return (T) dbResult;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
接下来定义装饰类,用于后期扩展二级缓存,或三级缓存
public interface AbstractDecoration extends ComponentCache {
}
定义二级缓存
这里的思路是:先通过key去查询redis中是否存在数据,如果存在则直接返回结果,不存在的话,再查询jvm内存中是否有结果,没有结果再继续往下查询db
但是这里我是通过super关键字去调用查询一级缓存,因为二级缓存是基于一级缓存进行扩展的
@Component
@Slf4j
public class RedisDecoration extends JvmComponentCache implements AbstractDecoration {
@Autowired
private RedisUtils redisUtils;
/**
* redis 二级缓存
*
* @param key key查询缓存
* @param clz 动态返回对象
* @param joinPoint 目标对象方法
* @param <T> 返回对象
* @return
*/
@Override
public <T> T getCacheEntity(String key, Class<T> clz, ProceedingJoinPoint joinPoint) {
log.info("二级缓存查询开始");
//二级缓存
T t1 = redisUtils.getEntity(key, clz);
if (t1 != null) {
log.info("查询二级缓存,{}", t1);
log.info("二级缓存查询结束");
return t1;
}
//一级缓存
T t2 = super.getCacheEntity(key, clz, joinPoint);
if (t2 != null) {
log.info("查询一级缓存,{}", t2);
redisUtils.setString(key, JSON.toJSONString(t2));
log.info("查询一级缓存结束");
return t2;
}
return null;
}
}
定义SunnyCache这个类,主要用于是方便外部调用的时候,其次如果有多个装饰类的话,可以将调用放在一个类中进行管理,方便后期维护
@Component
public class SunnyCache {
@Autowired
private RedisDecoration redisDecoration;
/**
* 根据key获取缓存数据
*
* @param key 获取缓存的key
* @param clz 动态返回对象
* @param joinPoint 通过代理获取目标对象
* @param <T> 返回类型
* @return
*/
public <T> T getCache(String key, Class<T> clz, ProceedingJoinPoint joinPoint) {
return redisDecoration.getCacheEntity(key, clz, joinPoint);
}
}
定义注解,通过注解来实现多级缓存控制
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) //允许通过java反射获取注解
@Documented
public @interface SunnyCacheAop {
}
实现注解,这里的思路是,拦截方法上加入注解方法,并获取目标对象,通过方法名加参数类型加参数的值拼装成key,存放入内存中
@Aspect
@Component
@Slf4j
public class SunnyCacheAopImpl {
@Autowired
private SunnyCache sunnyCache;
/**
* 拦截使用缓存注解
*
* @param joinPoint
*/
@Around(value = "@annotation(com.dream.sunny.aop.SunnyCacheAop)")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("AOP拦截->查询缓存数据开始");
//获取目标对象
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
//获取目标方法
Method method = methodSignature.getMethod();
//key = 方法名+方法参数类型+方法的参数的值
String key = method.getName() + Arrays.toString(method.getParameterTypes()) + Arrays.toString(joinPoint.getArgs());
Object cache = sunnyCache.getCache(key, method.getReturnType(), joinPoint);
log.info("AOP拦截->查询缓存数据结束");
return cache;
}
}
多级缓存框架以上就基本实现好了,接下来我们开始实现这个功能哈~
entity类
@Data
public class Student {
private Integer id;
private String name;
private Integer age;
}
mapper类
@Repository
public interface StudentMapper {
/**
* 查询用户
*
* @param userId
* @return
*/
@Select("select id,name,age from student s where s.id = #{userId}")
Student getStudent(@Param("userId") Integer userId);
}
service类
public interface StudentService {
/**
* 获取学生信息
*
* @param userId
* @return
*/
Student getStudent(Integer userId);
}
service实现类
@Service
@Slf4j
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
/**
* 获取学生
*
* @param userId
* @return
*/
@Override
@SunnyCacheAop //加上注解,拦截这个查询方法
public Student getStudent(Integer userId) {
log.info("业务执行开始");
Student mapperStudent = studentMapper.getStudent(userId);
System.out.println("业务执行结束");
return mapperStudent;
}
}
controller类
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/getStudent")
public String getStudent(@RequestParam("userId") Integer userId) {
Student student = studentService.getStudent(userId);
return JSON.toJSONString(student);
}
}
演示开始:
数据库数据
这里我本地redis数据,都被我清空了哈
当二级缓存和一级缓存都没有数据的执行效果:
当二级缓存有数据的效果图
看一下redis数据
到此手写多级缓存框架基本就到此结束啦