springboot下基于redis做自定义缓存标签,方法级缓存一贴搞定!

自用好几年的redis缓存标签,已经优化过N版,支持并发,拷进项目就能用,不用谢我

原理

拦截器读取方法的参数列表和参数值, 并以方法名和参数组合起来做为redis的key, 定期存取redis来降低数据库的读压力

一个类,一个标签,直接上代码



/**
 * @Author: Fcx
 * @Date: 2020/4/21 14:30
 * @Version 1.0
 */
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Author: Fcx
 * @Date: 2020/4/21 14:30
 * @Version 1.0
 */
@Aspect
@Component
@Slf4j
public class CacheServiceAspect {

    @Pointcut("@annotation(com.xxxxx.configurer.cache.UseCache)")
    public void dealCacheServiceCut() {
    }

    @Autowired
    private RedisTemplate redisTemplate;

    @Around(value = "dealCacheServiceCut()")
    public Object dealCacheService(ProceedingJoinPoint point) throws Throwable {
        try {
            Method method = getMethod(point);
            // 获取注解对象
            UseCache useCache = method.getAnnotation(UseCache.class);
            //所有参数
            Object[] args = point.getArgs();

            String fieldKey = parseKey(method, args);
            if (StrUtil.isEmpty(fieldKey)) {
                return point.proceed();
            }
            // 这里使用全部拼接是为了打印日志用, 方便查找问题, 如果觉得key太长可以MD5一下
            String cacheKey = useCache.totalKeyPreFix() + useCache.keyPrefix() + fieldKey;
            UseCache.CacheOperation cacheOperation = useCache.cacheOperation();
            if (cacheOperation == UseCache.CacheOperation.DQL) {
                return processDQL(point, useCache, cacheKey);
            }
            if (cacheOperation == UseCache.CacheOperation.DML) {
                return processDML(point, cacheKey);
            }
        } catch (Exception e) {
            log.error("dealCacheService error,JoinPoint:{}", point.getSignature(), e);
            System.err.println(e);
        }
        return point.proceed();
    }

    /**
     * 查询处理
     */
    private Object processDQL(ProceedingJoinPoint point, UseCache useCache, String cacheKey)
            throws Throwable {
        if (redisTemplate.hasKey(cacheKey)) {
            Object o = redisTemplate.opsForValue().get(cacheKey);
            if (useCache.printLog()) {
                log.info("{} enable cache service,has cacheKey:{} , return {}", point.getSignature(), cacheKey, o);
            }
            // 防止并发情况下, 取值时正好过期
            if (null != o) {
                return o;
            }
        }
        // 存值
        Object result = null;
        try {
            return result = point.proceed();
        } finally {
            redisTemplate.opsForValue().set(cacheKey, result, useCache.expireTime(), useCache.timeUnit());
            log.info("after {} proceed,save result to cache,redisKey:{},save content:{}", point.getSignature(), cacheKey, result);
        }
    }

    /**
     * 删除和修改处理
     */
    private Object processDML(ProceedingJoinPoint point, String cacheKey)
            throws Throwable {
        try {
            return point.proceed();
        } finally {
            // 删除掉原来在缓存中的数据,下次查询时就会刷新
            redisTemplate.delete(cacheKey);
        }
    }

    private Method getMethod(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        return method;
    }

    /**
     * 获取redis的key
     */
    private String parseKey(Method method, Object[] args) {
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u =
                new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < paraNameArr.length; i++) {
            sb.append(paraNameArr[i]).append(args[i]);
        }
        return sb.toString();
    }
}

  • 标签
package com.yiweikeji.food.configurer.cache;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**
 * @Author: Fcx
 * @Date: 2020/4/21 14:28
 * @Version 1.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UseCache {

    /**
     * 总前缀, 分布式项目防止key冲突, 一般使用项目名或项目简称固定写死
     * @return
     */
    String totalKeyPreFix() default "";

    /**
     * key前缀
     */
    String keyPrefix();
    /**
     * 过期时间 2分钟
     */
    int expireTime() default 60 * 2;

    /**
     * 是否打印日志
     */
    boolean printLog() default true;

    TimeUnit timeUnit() default TimeUnit.SECONDS;

    CacheOperation cacheOperation() default CacheOperation.DQL;

    /**
     * 缓存操作类型
     */
    enum CacheOperation {
        /**
         * 读库
         */
        DQL,
        /**
         * 写库
         */
        DML,
        ;
    }
}

如何使用

  • 使用场景
    一般在service层使用, 贴在实现类上, 达到整个方法的缓存目的
  • 使用示例:
    // 默认只需要填keyPrefix前缀  和cacheOperation缓存类型  加缓存就用DQL, 删缓存就用DML
    @UseCache(keyPrefix = "InstanceAwardServiceImpl_1",
            cacheOperation = UseCache.CacheOperation.DQL)
    @Override
    public List<InstanceAward> cacheList(Integer type, Long instanceId) {
        return this.list(
                new QueryWrapper<InstanceAward>()
                        .eq("type", type)
                        .eq("instance_id", instanceId)
                        .orderByAsc("sort_num")
        );
    }

是不是很简单? 如果觉得好用的话, 记得回来给我点个赞哈

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341