美文网首页
springboot下基于redis做自定义缓存标签,方法级缓存

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

作者: 烛火下的乌托邦丶 | 来源:发表于2022-01-07 19:11 被阅读0次

自用好几年的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")
        );
    }

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

相关文章