• 对redis缓存使用的注解针对list类型


    我们使用redis缓存大多数用的是差不多的模板,代码侵入性大,此处加个注解,方便使用。

    注解:

    package com.sd.outbound.common.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 注解 ListCacheData 用于简便处理需要进行缓存的操作
     * 注意 增加了全局缓存开关,参数为 global_list_cache_open_status_key,
     * 若是要关闭所有使用这个注解的缓存,可在nacos 配置中心或者其他配置文件配置 global_list_cache_open_status_key: false 即可关闭
     * 若是要关闭单个使用注解缓存的地方,在nacos 配置中心或者其他配置文件的地方 配置 prefix: false 即可
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface ListCacheData {
    
        /**
         * 前缀
         * @return
         */
        String prefix();
    
        /**
         * 作为key的参数是在方法的第几个参数
         * @return
         */
        int suffixParamIndex() default 0;
    
    
        /**
         * 后缀字段,,为空时候表示不需要后缀表达式,采用 SPEL
         * eg:
         *  1.不支持加固定后缀!!! 目前spEl数据源是来自方法入参,要是需要固定后缀,放置在前缀就好了;
         *  2.访问参数 比如 methodName(List<String userId, String pageId)  ->"#userId+'_'+#pageId";
         *  3.访问对象内属性 比如 methodName(UserBO userBO)  ->"#userBO.name";
         *  4.访问集合数据 比如 methodName(List<String> list) -> "#list.toString()";
         *
         * @return
         */
        String suffixExpression() default "";
    
        /**
         * 后缀字段,,为空时候表示不需要后缀表达式,采用 SPEL
         * eg:
         *  1.不支持加固定后缀!!! 目前spEl数据源是来自方法入参,要是需要固定后缀,放置在前缀就好了;
         *  2.访问参数 比如 methodName(List<String userId, String pageId)  ->"#userId+'_'+#pageId";
         *  3.访问对象内属性 比如 methodName(UserBO userBO)  ->"#userBO.name";
         *  4.访问集合数据 比如 methodName(List<String> list) -> "#list.toString()";
         *
         * @return
         */
        String suffixField();
    
    
    
        /**
         * 缓存时间 秒
         * @return
         */
        long expireSecond() default 300;
    
    
    }

    切面:

    package com.sd.outbound.core.aspect;
    
    import cn.hutool.core.util.StrUtil;
    import com.alibaba.fastjson.JSON;
    import com.sd.outbound.common.annotation.ListCacheData;
    import com.sd.outbound.core.CoreConstants;
    import com.sd.outbound.core.domain.bo.UserBO;
    import com.soudian.common.StringHelper;
    import com.soudian.utils.CollectionUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    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.core.LocalVariableTableParameterNameDiscoverer;
    import org.springframework.core.env.Environment;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.expression.EvaluationContext;
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.expression.spel.support.StandardEvaluationContext;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Type;
    import java.util.*;
    import java.util.concurrent.TimeUnit;
    
    
    @Slf4j
    @Aspect
    @Component
    public class ListCacheDataAspect {
    
        private ExpressionParser parser = new SpelExpressionParser();
    
        private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
    
    
        @Autowired
        StringRedisTemplate stringRedisTemplate;
    
        @Autowired
        private Environment env;
    
        @Around("@annotation(listCacheData)")
        public Object listCacheDataAround(ProceedingJoinPoint pjp, ListCacheData listCacheData) throws Throwable {
            String prefix = listCacheData.prefix();
    
            String currentCacheOpenStatus = env.getProperty(prefix);
            String globalCacheStatus = env.getProperty(CoreConstants.GLOBAL_LIST_CACHE_OPEN_STATUS_KEY);
            log.info("listCacheOpenStatus of {} = {}, globalListCacheStatus={}", prefix, currentCacheOpenStatus, globalCacheStatus);
            if (Boolean.FALSE.toString().toLowerCase().equals(globalCacheStatus) || Boolean.FALSE.toString().toLowerCase().equals(currentCacheOpenStatus)) {
                return pjp.proceed();
            }
    
            long expireSecond = listCacheData.expireSecond();
            int suffixParamIndex = listCacheData.suffixParamIndex();
    
            Method method = getMethod(pjp);
            Type genericReturnType = method.getGenericReturnType();
            Class<?> returnType = method.getReturnType();
    
            Object arg = null;
            if (suffixParamIndex >= pjp.getArgs().length || (arg = pjp.getArgs()[suffixParamIndex]) == null) {
                return pjp.proceed();
            }
    
            if (arg instanceof List) {
                List list = (List) arg;
                if (CollectionUtils.isEmpty(list)) {
                    return pjp.proceed();
                }
    
                List cachedList = new ArrayList();
                Map mapResult = new HashMap();
                List listResult = new ArrayList();
                Boolean isMap = false;
                if(returnType.getSimpleName().equals("Map")){
                    isMap = true;
                }
    
                    for (Object o : list) {
                        String key = StrUtil.join(StrUtil.COLON, prefix, parseSpel(o, listCacheData.suffixExpression()));
    
                        String data = stringRedisTemplate.opsForValue().get(key);
                        if (!StringHelper.isEmpty(data)) {
                            cachedList.add(o);
                            Object itemResult = JSON.parseObject(data, genericReturnType);
                            if(isMap){
                                Map itemMap = (Map) itemResult;
                                mapResult.put(o, itemMap.get(o));
                            }else{
                                listResult.add(itemResult);
                            }
                        }
                    }
    
                if (cachedList.size() < ((List<?>) arg).size()) {
                    for (Object o : cachedList) {
                        ((List<?>) arg).remove(o);
                    }
    
                    Object result = pjp.proceed();
                    if(result instanceof Map){
                        Map dbMapResult = (Map) result;
                        if(CollectionUtils.isEmpty(dbMapResult)){
                            return mapResult;
                        }
    
                        for (Object o : dbMapResult.keySet()) {
                            Map map = new HashMap(1, 1);
                            map.put(o, dbMapResult.get(o));
                            mapResult.put(o, dbMapResult.get(o));
    
                            stringRedisTemplate.opsForValue().set(StrUtil.join(StrUtil.COLON, prefix, parseSpel(o, listCacheData.suffixExpression())), JSON.toJSONString(map), expireSecond, TimeUnit.SECONDS);
                        }
                    }else if(result instanceof List){
                        List dbListResult = (List) result;
                        if(CollectionUtils.isEmpty(dbListResult)){
                            return listResult;
                        }
    
                        for (Object o : dbListResult) {
                            stringRedisTemplate.opsForValue().set(StrUtil.join(StrUtil.COLON, prefix, parseSpel(o, listCacheData.suffixField())), JSON.toJSONString(Arrays.asList(o)), expireSecond, TimeUnit.SECONDS);
                            listResult.add(o);
                        }
                    }
    
                    log.info("CacheData get data from db in method:{}", method.getName());
                        log.info("listCacheDataAround pjp absentList = {}", arg);
                }else {
                    return isMap ? mapResult: listResult;
                }
            }
    
            return pjp.proceed();
        }
    
        private Object parseSpel(Object item, String spel) {
            if(StringHelper.isEmpty(spel)){
                return item;
            }
            spel = "#item."+spel;
            EvaluationContext context = new StandardEvaluationContext();
            context.setVariable("item", item);
            try {
                Expression expression = parser.parseExpression(spel);
                return expression.getValue(context);
            } catch (Exception e) {
                return StrUtil.EMPTY;
            }
        }
    
        private static Method getMethod(ProceedingJoinPoint pjp) {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            if (method.getDeclaringClass().isInterface()) {
                try {
                    method = pjp
                            .getTarget()
                            .getClass()
                            .getDeclaredMethod(pjp.getSignature().getName(),
                                    method.getParameterTypes());
                } catch (SecurityException | NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            }
    
            return method;
        }
    }
  • 相关阅读:
    学习:大文件统计与排序
    共享库SidebySide之Windows Shared Assembly
    Bundle是个好东西
    所谓的代码段、数据段
    [design decision] common case vs. rare case
    如何给C++设计一个GC
    玩一把tesseract
    [design decision]让工具依赖于naming convention是个拙劣的做法
    监控域名可用性并自动发信
    调试lua代码
  • 原文地址:https://www.cnblogs.com/heisehenbai/p/15819301.html
Copyright © 2020-2023  润新知