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
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
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
2
3
4
5