Mybatis深入源码分析之基于【装饰设计模式】纯手写实现多级缓存框架

时间:2022-07-24
本文章向大家介绍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数据

到此手写多级缓存框架基本就到此结束啦