Cache统一缓存注解封装

2024/2/27 SpringBoot 框架

基于AOP实现@Cache注解封装

# 1、定义Cache注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {

    /**
     * 缓存键名,不设置则为方法名,需要加上参数,openKeyNameArgs开启即可
     * @return
     */
    String keyName() default "";

    /**
     * 是否开启参数化键名,也就是会将键名加上参数
     * 比如:方法名:getNameById,参数:id=564556,所有参数MD5加密后:3333,则缓存键名:getNameById_3333
     * @return
     */
    boolean openKeyNameArgs() default true;

    /**
     * 指定参数化键名,不指定则默认所有参数
     * 前提:openKeyNameArgs=true
     * @return
     */
    String[] keyNameArgs() default {};
    /**
     * 缓存时间,单位毫秒,默认30天:30*24*60*60*1000
     * @return
     */
    long cacheTime() default 2592000000L;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 2、AOP拦截封装

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Slf4j
public class CacheAspect {

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(com.xygalaxy.annotation.Cache)")
    public Object arround(ProceedingJoinPoint joinPoint) {
        try {
            // 获取注解信息
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Cache cache = method.getAnnotation(Cache.class);
            String keyName = cache.keyName();
            if(StrUtil.isBlank(keyName)){
                keyName = method.getName();
            }

            // 开启参数化键名
            if(cache.openKeyNameArgs()){
                keyName = keyName+"_"+this.getArgsMd5(joinPoint,signature.getParameterNames(),cache);
            }

            // 如果缓存命中
            if (redisCache.getCacheObject(keyName) != null) {
                // 接着请求
                String finalKeyName = keyName;
                RLock lock = redissonClient.getLock(finalKeyName);
                // 加入分布式锁,防止反复缓存,默认等待3s获取锁才能再次进行缓存,10s自动解锁
                if(!lock.isLocked()&&lock.tryLock(3000, 10000, TimeUnit.MILLISECONDS)){
                    threadPoolTaskExecutor.execute(()->{
                        try {
                            // 缓存
                            this.setRedisCache(finalKeyName,joinPoint.proceed(), cache.cacheTime());
                        } catch (Throwable e) {
                            log.error("缓存执行异常===>",e.getMessage());
                        }
                    });
                }
                return redisCache.getCacheObject(keyName);
            } else {
                Object proceed = joinPoint.proceed();
                // 缓存
                this.setRedisCache(keyName,proceed, cache.cacheTime());
                return proceed;
            }
        } catch (Throwable e) {
            log.error("缓存执行异常:{}",e.getMessage());
        }
        throw new AlmException("缓存执行异常");
    }

    /**
     * 设置缓存
     * @param keyName 键名
     * @param cacheValue 缓存数据
     * @param cacheTime 过期时间,毫秒
     */
    private void setRedisCache(String keyName,Object cacheValue,long cacheTime){
        if(cacheTime!=-1&&cacheTime>0){
            redisCache.setCacheObject(keyName,cacheValue,cacheTime, TimeUnit.MILLISECONDS);
        }else {
            redisCache.setCacheObject(keyName,cacheValue);
        }
    }

    /**
     * 获取参数并加密MD5
     * @param joinPoint
     * @param parameterNames
     * @return
     */
    private String getArgsMd5(ProceedingJoinPoint joinPoint,String[] parameterNames,Cache cache){
        StringBuilder argsBuilder = new StringBuilder();
        if(parameterNames.length>0){
            Object[] args = joinPoint.getArgs();
            String[] cacheArgs = cache.keyNameArgs();
            for (int i = 0; i < parameterNames.length; i++) {
                Object parameterValue = args[i];
                if(cacheArgs.length>0){
                    String parameterName = parameterNames[i];
                    for (String cacheArg : cacheArgs) {
                        if(cacheArg.equals(parameterName)){
                            argsBuilder.append(parameterValue);
                        }
                    }
                }else {
                    argsBuilder.append(parameterValue);
                }
            }
        }else {
            return "";
        }
        return DigestUtil.md5Hex(argsBuilder.toString());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

# 3、测试使用

@GetMapping("/test")
@Cache
public String test(String monthDate) {
    return "success";
}
1
2
3
4
5