精讲23种设计模式-基于装饰模式~设计多级缓存框架


基于装饰模式设计多级缓存

一、装饰模式
1. 回顾多级缓存基本概念

在实际开发项目,为了减少数据库的访问压力,我们都会将数据缓存到内存中
比如:Redis(分布式缓存)、EHCHE(JVM内置缓存).
例如在早起中,项目比较小可能不会使用Redis做为缓存,使用JVM内置的缓存框架,
项目比较大的时候开始采用Redis分布式缓存框架,这时候需要设计一级与二级缓存。

2. 装饰模式基本的概念

不改变原有代码的基础之上,新增附加功能
在这里插入图片描述

3. 装饰模式应用场景

多级缓存设计、mybatis中一级与二级缓存、IO流

4. 装饰者模式定义

(1)抽象组件:定义一个抽象接口,来规范准备附加功能的类
(2)具体组件:将要被附加功能的类,实现抽象构件角色接口
(3)抽象装饰者:持有对具体构件角色的引用并定义与抽象构件角色一致的接口
(4)具体装饰:实现抽象装饰者角色,负责对具体构件添加额外功能。

5. 基于Map手写Jvm内置缓存
package com.gblfy.utils;

import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 基于Map手写Jvm内置缓存
 *
 * @Author gblfy
 * @Date 2022-03-15 21:01
 **/
@Component
public class JvmMapCacheUtils {
    
    

    /**
     * 并发缓存容器
     */
    private static Map<String, String> cacheList = new ConcurrentHashMap<String, String>();


    /**
     * 从本地缓存中根据key获取值
     *
     * @param key 缓存key
     * @param t   返回对象类型
     * @param <T> 返回对象
     * @return
     */
    public static <T> T getCache(String key, Class<T> t) {
    
    
        //    缓存存储对象
        String jsonValue = cacheList.get(key);
        return JSONObject.parseObject(jsonValue, t);
    }

    /**
     * 缓存数据到本地jvm中
     *
     * @param key
     * @param val
     */
    public static void putCache(String key, Object val) {
    
    
        String jsonValue = JSONObject.toJSONString(val);
        cacheList.put(key, jsonValue);
    }

    /**
     * 根据缓存key删除指定缓存数据
     *
     * @param cacheKey
     */
    public static void removeCacheCacheKey(String cacheKey) {
    
    
        cacheList.remove(cacheKey);
    }

    /**
     * 更新缓存数据
     *
     * @param key
     * @param val
     */
    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;

/**
 * redis工具类
 *
 * @author gblfy
 * @date 2022-03-15
 */
@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;
    }
}

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 BaseMapper<UserEntity> {
    
    
}
2.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;

/**
 * 使用装饰模式~查询用户数据
 *
 * @Author gblfy
 * @Date 2022-03-15 21:12
 **/
@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中是否存在,如果存在,直接返回缓存数据

三、设计多级缓存框架
3.1. 缓存容器抽象
package com.gblfy.decoration;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 缓存容器抽象
 *
 * @Author gblfy
 * @Date 2022-03-15 21:42
 **/
public interface ComponentCache {
    
    

    /**
     * 根据key查询缓存数据
     *
     * @param key       缓存的key
     * @param t         传入对象参数类型
     * @param joinPoint 目标方法内容
     * @param <T>       返回的对象类型
     * @return
     */
    <T> T getCacheEntity(String key, Class<T> 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;

/**
 * 一级缓存查询处理类
 *
 * @Author gblfy
 * @Date 2022-03-15 21:45
 **/
@Component
public class JvmComponentCache implements ComponentCache {
    
    

    // @Autowired
    // private UserMapper userMapper;

    @Override
    public <T> T getCacheEntity(String key, Class<T> 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 {
    
    
            /**
             * 当一级缓存不存在时,查询我们的db,相当于userMapper.selectById(userId)这一行代码
             * 1.通过aop直接获取目标对象的方法
             * 解析:
             * 目的:执行joinPoint.proceed();这一行就相当于执行目标方法,为了做成抽象通用的,
             * 方案:采用aop来实现仅此而已
             */
            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;

/**
 * 二级缓存抽象接口
 *
 * @author gblfy
 * @date 2022-03-16
 */
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;

/**
 * 二级缓存查询处理类
 *
 * @Author gblfy
 * @Date 2022-03-15 21:50
 **/
@Component
public class RedistDecorate extends JvmComponentCache implements AbstractDecorate {
    
    
    @Autowired
    private RedisUtils redisUtils;
    // @Autowired
    // private JvmComponentCache jvmComponentCache;

    @Override
    public <T> T getCacheEntity(String key, Class<T> 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.*;

/**
 * 二级缓存查询aop拦截
 *
 * @author gblfy
 * @date 2022-03-15
 */
@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;

/**
 * 实现二级缓存查询aop拦截
 * 自定义ExtGblfyCache注解
 *
 * @author gblfy
 * @date 2022-03-15
 */
@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;

/**
 * 二级缓存外壳封装
 *
 * @Author gblfy
 * @Date 2022-03-15 22:01
 **/
@Component
public class GblfyCache {
    
    

    @Autowired
    private RedistDecorate redistDecorate;

    public <T> T getCacheEntity(String key, Class<T> 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;

/**
 * 使用装饰模式~查询用户数据
 *
 * @Author gblfy
 * @Date 2022-03-15 21:12
 **/
@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/

猜你喜欢

转载自blog.csdn.net/weixin_40816738/article/details/123521172