首页 > 技术文章 > 项目-无侵入代码方式使用Redis实现缓存功能

fantongxue 2021-07-16 14:49 原文

一,情景介绍

公司新需求,在查询接口的manager层要加入redis缓存,只要通过manager层的增删改查方法,统统进行缓存处理。

基于这个需求,我写了一个aop切面,具体实现逻辑如下

ProceedingJoinPoint的操作见文章:https://www.cnblogs.com/draymond/p/12670123.html

二,思路梳理

大致分为以下几个步骤,不多,很简单

  • 自定义注解,凡是在Manager层加该注解的方法,都要进行缓存
  • aop切面,切该注解
  • 判断操作类型(增删改查),得到key值
  • 对操作结果进行缓存

三,代码实践

1,自定义注解

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)//选择运行环境
public @interface E2eCache {

    //查询条件(查询详情的主键)
    String key();

    /**
     * 方法操作
     * 默认为查询操作 新增指定为[add] 更新指定为[edit] 删除指定为[del] 查询指定为[get]
     * @return
     */
    String operate() default "get";
}

2,切面类

/**
 * 	redis在manager层的切面类,作为缓存机制,达到以下目的
 *      1,判断当前环境是否存在redis,如果存在,则加载缓存机制,如果不存在,则按原逻辑进行
 *      2,增删改查均加入redis缓存
 *      3,考虑对原有代码不侵入的原则
 */
@Aspect
@Component
public class ManagerAspect {

    @Value("spring.redis.host")
    private String redisHost;

    //TODO 模拟redis缓存,调试通过放RedisTemplate
    private static Map<String,Object> redisTemplate=new HashMap<>();

    private Logger logger= LoggerFactory.getLogger(ManagerAspect.class);

    @Pointcut("@annotation(com.dayouzc.rediscache.annotation.E2eCache)")
    private void cutMethod(){

    }


    @Around("cutMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //取注解的操作值
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //判断当前方法是否是在Manager类中
        // todo 目前暂时是通过判断当前类是否存在@Repository注解,以及方法名是否包含Manager字符串
        Class<?> declaringClass = signature.getMethod().getDeclaringClass();
        String declaringClassName = declaringClass.getName();
        //如果该方法不是在Manager类,不缓存
        if(!declaringClass.isAnnotationPresent(Repository.class) || !declaringClassName.contains("Manager")){
            return joinPoint.proceed();
        }
        // 1,判断是否存在redis环境
        if(StringUtils.isBlank("test")){
            logger.info("======== 当前环境redis不存在,缓存机制失效 ==========");
            //原逻辑处理
            return joinPoint.proceed();
        }else{
            logger.info("======== 当前环境redis正常,开启Manger层缓存 ==========");
            //得到request请求
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            //2,走redis缓存

            E2eCache annotation = signature.getMethod().getAnnotation(E2eCache.class);
            //操作类型
            String operate = annotation.operate();
            //key的名称
            String id = annotation.key();
            //方法名
            String methodName = joinPoint.getSignature().getName();
            //原方法返回值类型
            Class returnType = signature.getReturnType();
            if(operate.equals("get")){
                //查询详情
                JSONObject jsonObject = getByArgs(joinPoint, request);
                //根据返回值类型返回对应的实体对象
                Object object = JSONObject.toJavaObject(jsonObject, returnType);
                return object;
            }else if(operate.equals("add")){
                //新增
                JSONObject jsonObject = addOperation(joinPoint, request);
                Object object = JSONObject.toJavaObject(jsonObject, returnType);
                return object;
            }else if(operate.equals("edit")){
                //修改
                JSONObject jsonObject = editOperate(joinPoint, request);
                Object object = JSONObject.toJavaObject(jsonObject, returnType);
                return object;
            }else if(operate.equals("del")){
                //删除
                delOperate(joinPoint,request);
                return null;
            }else{
                //原逻辑处理
                return joinPoint.proceed();
            }
        }
    }

    /**
     * 处理删除
     * @param joinPoint
     * @param request
     */
    private void delOperate(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
        //删除缓存
        String key = ManagerCacheUtil.createKey(joinPoint, request);
        //判断缓存中是否有数据
        //todo 这里map提代redis
        boolean hasKey = redisTemplate.containsKey(key);
        //删除旧的缓存
        if(hasKey){
            logger.info("======== 删除旧缓存数据 ==========");
            redisTemplate.remove(key);
        }
    }

    /**
     * 处理更新
     * @param joinPoint
     * @param request
     */
    private JSONObject editOperate(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
        //方法返回值
        Object object = null;
        JSONObject result = null;
        try {
            //执行方法
            object = joinPoint.proceed();
            result=(JSONObject) JSONObject.toJSON(object);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        String key = ManagerCacheUtil.createKey(joinPoint,object);
        //判断缓存中是否有数据
        //todo 这里map提代redis
        boolean hasKey = redisTemplate.containsKey(key);
        //删除旧的缓存
        if(hasKey){
            logger.info("======== 删除旧缓存数据 ==========");
            redisTemplate.remove(key);
        }

        //存储redis
        logger.info("======== 新数据更新到redis缓存 ==========");
        redisTemplate.put(key,JSONObject.toJSONString(result));
        return result;
    }

    /**
     * 处理查询
     * @param joinPoint
     * @param request
     */
    private JSONObject getByArgs(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
        //得到缓存key
        String key = ManagerCacheUtil.createKey(joinPoint, request);
        //判断缓存中是否有数据
        //todo 这里map提代redis
        boolean hasKey = redisTemplate.containsKey(key);
        JSONObject jsonObject = null;
        //如果存在key,则取缓存数据并返回
        if(hasKey){
            logger.info("======== 缓存数据存在,返回缓存数据 ==========");
            String value = (String) redisTemplate.get(key);
            //返回
            return JSONObject.parseObject(value);
        }else{
            logger.info("======== 无缓存数据 ==========");
            try {
                 jsonObject=(JSONObject) JSONObject.toJSON(joinPoint.proceed());
                //存储新的查询结果
                logger.info("======== 新数据更新到redis缓存 ==========");
                redisTemplate.put(key,JSONObject.toJSONString(jsonObject));
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return jsonObject;
        }
    }

    /**
     * 处理新增
     * @param joinPoint
     */
    private JSONObject addOperation(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
        //方法返回值
        Object object = null;
        JSONObject result = null;
        try {
            //执行方法
            object = joinPoint.proceed();
             result=(JSONObject) JSONObject.toJSON(object);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        String key = ManagerCacheUtil.createKey(joinPoint,object);
        //存储redis
        logger.info("======== 新数据更新到redis缓存 ==========");
        redisTemplate.put(key,JSONObject.toJSONString(result));
        return result;
    }
}

3,生成key的工具类

public class ManagerCacheUtil {
    private static Logger logger= LoggerFactory.getLogger(ManagerCacheUtil.class);

    /**
     * 生成key  此方法供查询和删除方法使用
     * 定义key的生成规则
     * TODO 目前生成规则:manager类名 + id值(查询条件)
     * @param joinPoint
     * @param request
     */
    public static String createKey(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        E2eCache annotation = signature.getMethod().getAnnotation(E2eCache.class);
        //key的名称
        String id = annotation.key();
        // 下面两个数组中,参数值和参数名的个数和位置是一一对应的
        Object[] objects = joinPoint.getArgs(); // 参数值
        String[] argNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames(); // 参数名
        String key_value = StringUtils.EMPTY;
        if(argNames!=null && argNames.length>0){
            //如果参数名不在参数名列表中,1,该参数名在对象中 2,key值不存在
            if(!Arrays.asList(argNames).contains(id)){
                //遍历参数列表中的参数
                for(Object obj:objects){
                    if(obj instanceof String){
                        continue;
                    }else if(obj instanceof Integer){
                        continue;
                    }
                    key_value = fromObjectToGetKeyStringValue(obj, id);
                    if(!StringUtils.equals(key_value,"find not parameter")){
                        //在实体类中找到了参数,并得到了参数值result
                        break;
                    }
                }
                if(StringUtils.isEmpty(key_value) || StringUtils.equals(key_value,"find not parameter")){
                    throw new RuntimeException("未找到key值对应的参数");
                }
            }else{
                //参数名为正常的String或Integer
                //查找key所在数组的索引
                int index=0;
                for(int i=0;i<argNames.length;i++){
                    if(StringUtils.equals(id,argNames[i])){
                        break;
                    }else{
                        i++;
                    }
                }
                //取key值
                Object id_value = objects[index];
                //object转具体的类型并且得到id_value的string类型的值
                key_value = fromObjectToGetKeyStringValue(id_value,id);
            }
        }
        //校验key生成状态
        if(StringUtils.isBlank(key_value) || StringUtils.equals(key_value,"find not parameter")){
            throw new RuntimeException("生成key失败,key值为空");
        }
        //类名
        String className = signature.getMethod().getDeclaringClass().getName();
        //截取类名 只需要类名,不需要包名
        String newClassName = className.substring(className.lastIndexOf(".")+1);
        //方法名
        String methodName = signature.getMethod().getName();
        //组装key
        String key = newClassName + ":" + key_value;
        logger.info("======== 生成key:" + key + " ==========");
        return key;
    }

    /**
     * 生成key
     * 供新增和更新方法使用
     * 原因是新增和更新的参数不一定存在key的value值,只能规定新增和更新方法的返回值为新增后的对象或更新后的对象
     * @param object 返回实体对象
     */
    public static String createKey(ProceedingJoinPoint joinPoint,Object object){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        E2eCache annotation = signature.getMethod().getAnnotation(E2eCache.class);
        //key的名称
        String id = annotation.key();
        String key_value = fromObjectToGetKeyStringValue(object, id);
        if(StringUtils.equals(key_value,"find not parameter")){
           throw new RuntimeException("未找到key值对应的参数");
        }
        //类名
        String className = signature.getMethod().getDeclaringClass().getName();
        //截取类名 只需要类名,不需要包名
        String newClassName = className.substring(className.lastIndexOf(".")+1);
        //方法名
        String methodName = signature.getMethod().getName();
        //组装key
        String key = newClassName + ":" + key_value;
        logger.info("======== 生成key:" + key + " ==========");
        return key;
    }

    /**
     * 递归得到key的value值,从object中寻找参数名为keyName的属性,并返回keyName属性的参数值,如果没有找到,返回 find not parameter 字符串
     * @param id_value Object形式
     * @param keyName key名称
     * @return
     */
    public static String fromObjectToGetKeyStringValue(Object id_value,String keyName){
        String result = StringUtils.EMPTY;
        //todo 暂时限制key值仅支持integer和string类型以及实体类
        if(id_value instanceof Integer){
            //参数为integer
            result=String.valueOf(id_value);
            return result;
        }else if(id_value instanceof String){
            //参数为string
            result=id_value.toString();
            return result;
        }else if(id_value instanceof Object){
            //参数为domain实体类
            Class<?> aClass = id_value.getClass();
            //所有属性
            Field[] declaredFields = aClass.getDeclaredFields();
            for(Field field:declaredFields){
                if(StringUtils.equals(keyName,field.getName())){
                    //找到了匹配的属性
                    Method method = null;
                    //属性首字母大写,拼接方法名
                    String methodName = "get" + captureName(keyName);
                    try {
                        //执行get方法得到属性值
                        method=aClass.getMethod(methodName);
                        Object fieldValue = method.invoke(id_value);
                        //递归调用
                        String fieldVal = fromObjectToGetKeyStringValue(fieldValue, keyName);
                        return fieldVal;
                    } catch (NoSuchMethodException e) {
                        logger.error("没有找到对应的get方法",e);
                    } catch (IllegalAccessException e) {
                        logger.error("执行反射方法取属性值错误",e);
                    } catch (InvocationTargetException e) {
                        logger.error("执行反射方法取属性值错误",e);
                    }
                }
            }
            return "find not parameter";
        }else{
            return "find not parameter";
        }
    }

    //首字母大写
    private static String captureName(String name) {
        name = name.substring(0, 1).toUpperCase() + name.substring(1);
        return  name;
    }
}

四,测试

列出测试代码,和测试结果

Manager层

	@E2eCache(key = "linkId",operate = "get")
    public EbCmservLink getById(EbCmservLink ebCmservLink){
        return testMapper.getLinkDetail(ebCmservLink.getLinkId());
    }

    @E2eCache(key = "linkId",operate = "add")
    public EbCmservLink addLink(EbCmservLink ebCmservLink){
        ebCmservLink.setLinkId(UUID.randomUUID().toString().replaceAll("-",""));
        ebCmservLink.setCmId("test2345235");
        ebCmservLink.setCmName("中医理疗测试胶囊");
        ebCmservLink.setCminfoId("test9asdfu0sdf9");
        ebCmservLink.setCminfoName("test商品实例");
        int i = testMapper.addLinkDetail(ebCmservLink);
        if(i>0){
            return ebCmservLink;
        }else{
            return null;
        }
    }

    @E2eCache(key = "linkId",operate = "edit")
    public EbCmservLink updateLink(EbCmservLink ebCmservLink){
        int i = testMapper.updateLinkDetail(ebCmservLink);
        if(i>0){
            return testMapper.getLinkDetail(ebCmservLink.getLinkId());
        }else{
            return null;
        }
    }

Controller层

@RestController
public class TestController {
    @Autowired
    private TestManager manager;
    @GetMapping("/get")
    public EbCmservLink get(String linkId){
        EbCmservLink link=new EbCmservLink();
        link.setLinkId(linkId);
        return manager.getById(link);
    }
    @GetMapping("/insert")
    public EbCmservLink insert(EbCmservLink ebCmservLink){
        EbCmservLink link = manager.addLink(ebCmservLink);
        return link;
    }
    @GetMapping("/update")
    public EbCmservLink update(EbCmservLink ebCmservLink){
        EbCmservLink link = manager.updateLink(ebCmservLink);
        return link;
    }
}

推荐阅读