对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;
    }
}

 

上一篇:SpringBoot聚合项目——idea


下一篇:spring boot/spring cloud 集成redis sentinel 哨兵集群