1. 回顾多级缓存基本概念
在实际开发项目,为了减少数据库的访问压力,我们都会将数据缓存到内存中比如:Redis(分布式缓存)、EHCHE(JVM内置缓存)。
例如在早起中,项目比较小可能不会使用Redis做为缓存,使用JVM内置的缓存框架,项目比较大的时候开始采用Redis分布式缓存框架,这时候需要设计一级与二级缓存。
2. 装饰模式基本的概念
不改变原有代码的基础之上,新增附加功能。
3. 装饰模式应用场景
- 多级缓存设计
- mybatis中一级与二级缓存
- IO流
4. 装饰者模式定义
(1)抽象组件:定义一个抽象接口,来规范准备附加功能的类
(2)具体组件:将要被附加功能的类,实现抽象构件角色接口
(3)抽象装饰者:持有对具体构件角色的引用并定义与抽象构件角色一致的接口
(4)具体装饰:实现抽象装饰者角色,负责对具体构件添加额外功能。
5. 基于Map手写Jvm内置缓存
package com.tostyle.cache.utils;
import com.alibaba.fastjson.JSONObject;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 基于Jvm内置缓存
* @author tostyle
* 2022/4/8 9:17
*/
public class JvmMapCacheUtils {
private static Map<String, String> cacheMap = new ConcurrentHashMap<String, String>();
/**
* 添加缓存
* @param key
* @param object
*/
public static void putEntity(String key, Object object) {
cacheMap.put(key, JSONObject.toJSONString(object));
}
/**
* 获取缓存
* @param key
* @param t
* @param <T>
* @return
*/
public static <T> T getEntity(String key, Class<T> t) {
String json = cacheMap.get(key);
return JSONObject.parseObject(json, t);
}
}
6. 简单手写一级与二级缓存效果
/**
* 原始的二级缓存代码示例
* @param userId
* @return
*/
@GetMapping("/getUserInfoCacheOld")
public UserInfo getUserInfoCacheOld(Integer userId) {
//方法名称+参数名称+参数内容
String key = "getUserInfoCacheOld(Integer)" + userId;
// 先查询二级缓存
UserInfo redisUserInfo = redisUtils.getEntity(key, UserInfo.class);
if (redisUserInfo != null) {
return redisUserInfo;
}
// 再查询一级缓存
UserInfo jvmUserInfo = JvmMapCacheUtils.getEntity(key, UserInfo.class);
if (jvmUserInfo != null) {
//如果有,将数据缓存到redis中
redisUtils.putEntity(key, jvmUserInfo);
return jvmUserInfo;
}
// 查询数据库
UserInfo dbUserInfo = userInfoMapper.getUserInfo(userId);
if(dbUserInfo==null){
return null;
}
JvmMapCacheUtils.putEntity(key,dbUserInfo);
return dbUserInfo;
}
7. 基于装饰模式实现多级缓存优化
7.1 一级缓存
public interface ComponentCache {
<T> T getEntity(String key, Class<T> T, ProceedingJoinPoint joinPoint);
}
package com.tostyle.cache.decorate.impl;
import com.tostyle.cache.decorate.ComponentCache;
import com.tostyle.cache.utils.JvmMapCacheUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
/**
* 一级缓存
*
* @author tostyle
* 2022/4/8 10:27
*/
@Component
public class JvmComponentCache implements ComponentCache {
@Override
public <T> T getEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint) {
T entity = JvmMapCacheUtils.getEntity(key, t);
if (entity != null) {
return entity;
}
try {
//利用Aop调用具体的查询数据方法
Object dbProceed = joinPoint.proceed();
if (dbProceed == null) {
return null;
}
JvmMapCacheUtils.putEntity(key, dbProceed);
return (T) dbProceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
7.2 新增二级缓存
package com.tostyle.cache.decorate;
/**
* @author tostyle
* 2022/4/8 10:33
*/
public interface AbstractDecorate extends ComponentCache{
}
package com.tostyle.cache.decorate.ext;
import com.tostyle.cache.decorate.AbstractDecorate;
import com.tostyle.cache.decorate.impl.JvmComponentCache;
import com.tostyle.cache.utils.RedisUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 二级缓存
*
* @author tostyle
* 2022/4/8 10:34
*/
@Component
public class RedisDecorate extends JvmComponentCache implements AbstractDecorate {
@Autowired
private RedisUtils redisUtils;
@Override
public <T> T getEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint) {
// 先查询二级缓存
T redisUser = redisUtils.getEntity(key, t);
if (redisUser != null) {
return (T) redisUser;
}
// 如果一级缓存存在数据
T jvmUser = super.getEntity(key, t, joinPoint);
if (jvmUser == null) {
return null;
}
// 将该缓存数据放入到二级缓存中
redisUtils.putEntity(key, jvmUser);
return (T) jvmUser;
}
}
7.3使用一级与二级缓存
package com.tostyle.cache.decorate;
import com.tostyle.cache.decorate.ext.RedisDecorate;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author tostyle
* 2022/4/8 10:38
*/
@Component
public class MultiCache {
@Autowired
private RedisDecorate redisDecorate;
/**
* 获取一级和二级缓存
* @param key
* @param t
* @param joinPoint
* @param <T>
* @return
*/
public <T> T getCacheEntity(String key, Class<T> t, ProceedingJoinPoint joinPoint) {
return redisDecorate.getEntity(key, t, joinPoint);
}
}
7.4 Aop与自定义注解
package com.tostyle.cache.aop;
import java.lang.annotation.*;
/**
* 自定义缓存注解
* @author tostyle
* 2022/4/8 10:40
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExtMultiCache {
}
package com.tostyle.cache.aop;
import com.tostyle.cache.decorate.MultiCache;
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
*/
@Aspect
@Component
@Slf4j
public class ExtAsyncAop {
@Autowired
private MultiCache multiCache;
/**
* 使用Aop拦截我们的方法上是否使用缓存注解
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around(value = "@annotation(com.tostyle.cache.aop.ExtMultiCache)")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
// 获取目标方法
Method targetMethod = methodSignature.getMethod();
// 表示我们目标方法 getUser(Integer userId) {
String key = targetMethod.getName() + Arrays.toString(targetMethod.getParameterTypes()) +
Arrays.toString(joinPoint.getArgs());
return multiCache.getCacheEntity(key, targetMethod.getReturnType(), joinPoint);
}
}
7.5 测试
@GetMapping("/getUserInfo")
@ExtMultiCache
public UserInfo getUserInfo(Integer userId) {
return userInfoMapper.getUserInfo(userId);
}
8.完整代码
https://gitee.com/todostyle/springcloud.git