美文网首页
Mybatis深入源码分析之基于【装饰设计模式】纯手写实现多级缓

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

作者: Sunny捏 | 来源:发表于2020-07-19 15:53 被阅读0次

    前言:设计模式源于生活

    什么是装饰模式

    在不改变原有对象的基础上附加功能,相比生成子类更灵活。

    装饰者模式应用场景

    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数据


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

    相关文章

      网友评论

          本文标题:Mybatis深入源码分析之基于【装饰设计模式】纯手写实现多级缓

          本文链接:https://www.haomeiwen.com/subject/ggxrkktx.html