前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis深入源码分析之基于【装饰设计模式】纯手写实现多级缓存框架

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

作者头像
黎明大大
发布2020-09-08 16:28:39
3750
发布2020-09-08 16:28:39
举报
文章被收录于专栏:java相关资料java相关资料

前言:设计模式源于生活

什么是装饰模式

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

装饰者模式应用场景

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工具类
代码语言:javascript
复制
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工具类
代码语言:javascript
复制
@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;
    }
}
定义一个抽象组件,用于构建我们的骨架
代码语言:javascript
复制
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内存中

代码语言:javascript
复制
@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;
    }
}
接下来定义装饰类,用于后期扩展二级缓存,或三级缓存
代码语言:javascript
复制
public interface AbstractDecoration extends ComponentCache {

}
定义二级缓存

这里的思路是:先通过key去查询redis中是否存在数据,如果存在则直接返回结果,不存在的话,再查询jvm内存中是否有结果,没有结果再继续往下查询db 但是这里我是通过super关键字去调用查询一级缓存,因为二级缓存是基于一级缓存进行扩展的

代码语言:javascript
复制
@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这个类,主要用于是方便外部调用的时候,其次如果有多个装饰类的话,可以将调用放在一个类中进行管理,方便后期维护
代码语言:javascript
复制
@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);
    }
}
定义注解,通过注解来实现多级缓存控制
代码语言:javascript
复制
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) //允许通过java反射获取注解
@Documented
public @interface SunnyCacheAop {
}
实现注解,这里的思路是,拦截方法上加入注解方法,并获取目标对象,通过方法名加参数类型加参数的值拼装成key,存放入内存中
代码语言:javascript
复制
@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类
代码语言:javascript
复制
@Data
public class Student {

    private Integer id;

    private String name;

    private Integer age;
}
mapper类
代码语言:javascript
复制
@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类
代码语言:javascript
复制
public interface StudentService {

    /**
     * 获取学生信息
     *
     * @param userId
     * @return
     */
    Student getStudent(Integer userId);
}
service实现类
代码语言:javascript
复制
@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类
代码语言:javascript
复制
@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数据

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:设计模式源于生活
    • 什么是装饰模式
      • 装饰者模式应用场景
      • 装饰者模式定义
        • 装饰模式和代理模式区别?
          • 多级缓存框架的设计
          • 首先在实现多级缓存框架之前,我先大概讲解一下我的实现的思路原理
          • 首先展示一波,我的整体项目结构
      • 多级缓存框架实现开始
      相关产品与服务
      云数据库 Redis
      腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档