【项目】STC-stcdatacache

[项目系列文章说明]: 该类型的文章是对项目的实现方案和部分代码进行说明.

除了使用Spring cache之外, 项目对自定义缓存扩展有一定需求的情况下, 自己动手编写一个针对方法级别的缓存(可扩展支持到类,但不易维护, 不建议使用类级别的).
结合STC-stcconfig动态配置系统, 可实现缓存的动态删除, 缓存开关实时生效.

代码地址: https://github.com/lvxingzhi/stcdatacache

系统实现原理

利用Spring 提供的AOP机制,实现对方法的扩展.
利用Spel表达式实现方法入参与缓存key动态匹配.
预定义缓存接口,实现对不同缓存方式的兼容.
异常处理保证业务的可用性.
整理并不复杂, 流程图略.

依赖

1, Spring
2, 任意分布式缓存

目录结构

annotation: 注解
aspect: 注解处理器
cache: 缓存接口定义和本地实现案例
test: 测试Demo

核心代码

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
/**
* STCDataCache处理器
*
* @Author xingzhi.lv
* @Version 2.1
* @Date 2021/11/3 11:02
*/
@Aspect
@Component
public class STCDataCacheAspect {
private static final Logger logger = LoggerFactory.getLogger(STCDataCacheAspect.class);
public static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
public static final String KEY_SPLIT = "_";
private STCCacheTemplate cacheTemplate = new STCCacheLocalTemplate();
public static final String STCNULL = "STCNULL";
// 缓存切面
@Pointcut("@annotation(com.note.stcdatacache.annotation.STCDataCache)")
public void methodCachePoint() {
}

// 删除缓存切面
@Pointcut("@annotation(com.note.stcdatacache.annotation.STCDataCacheDelete)")
public void methodCacheDeletePoint() {
}

@Around(value = "methodCachePoint()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
STCDataCache cacheAnno = AnnotationUtils.findAnnotation(method, STCDataCache.class);
String keyEL = cacheAnno.cacheKey();
Object[] args = joinPoint.getArgs();
String[] argNames = signature.getParameterNames();
// 使用Spel解析
EvaluationContext context = new StandardEvaluationContext();
String cacheKey;
if (Strings.isNotEmpty(keyEL)) {
if (args != null && args.length > 0) {
for (int i = 0; i < argNames.length; i++) {
context.setVariable(argNames[i], args[i]);
}
}
Object obj = EXPRESSION_PARSER.parseExpression(keyEL).getValue(context);
cacheKey = obj.toString();
} else {
String className = joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
cacheKey = className + KEY_SPLIT + methodName;
}
String group = cacheAnno.group();
cacheKey = group + KEY_SPLIT + cacheKey;
// 读取缓存数据
Object cacheResult = cacheTemplate.get(cacheKey);
if (cacheResult != null && !STCNULL.equals(cacheResult)) {
return cacheResult;
}
// 空占位符
if (STCNULL.equals(cacheResult)) {
return null;
}
final Object result = joinPoint.proceed();
if (Objects.isNull(result)) {
cacheTemplate.set(cacheKey, STCNULL);
} else {
cacheTemplate.set(cacheKey, result);
}
return result;
} catch (Throwable ta) {
logger.error("STCDataCache read cache error", ta);
return joinPoint.proceed();
}
}

@Around(value = "methodCacheDeletePoint()")
public Object deleteAround(ProceedingJoinPoint joinPoint) throws Throwable {
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
STCDataCacheDelete cacheAnno = AnnotationUtils.findAnnotation(method, STCDataCacheDelete.class);
String keyEL = cacheAnno.cacheKey();
Object[] args = joinPoint.getArgs();
String[] argNames = signature.getParameterNames();
// 使用Spel解析
EvaluationContext context = new StandardEvaluationContext();
String cacheKey;
if (Strings.isNotEmpty(keyEL)) {
if (args != null && args.length > 0) {
for (int i = 0; i < argNames.length; i++) {
context.setVariable(argNames[i], args[i]);
}
}
Object obj = EXPRESSION_PARSER.parseExpression(keyEL).getValue(context);
cacheKey = obj.toString();
} else {
String className = joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
cacheKey = className + KEY_SPLIT + methodName;
}
String group = cacheAnno.group();
cacheKey = group + KEY_SPLIT + cacheKey;
// 删除缓存数据
cacheTemplate.delete(cacheKey);
} catch (Throwable ta) {
logger.error("STCDataCache delete cache error", ta);
}
return joinPoint.proceed();
}

public STCCacheTemplate getCacheTemplate() {
return cacheTemplate;
}

public void setCacheTemplate(STCCacheTemplate cacheTemplate) {
this.cacheTemplate = cacheTemplate;
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@SpringBootTest
class StcdatacacheApplicationTests {

@Resource
private DemoService demoService;

@Test
void contextLoads() {
// 无缓存
String demoName1 = demoService.findDemoName(58);
System.out.println(demoName1);
// 查缓存
String demoName2 = demoService.findDemoName(58);
System.out.println(demoName2);
// 删除缓存
demoService.deleteDemoDelete(58);
// 无缓存
String demoName3 = demoService.findDemoName(58);
System.out.println(demoName3);
}

}

结果

1
2
3
DemoName : -2094750780
DemoName : -2094750780 // 读取缓存
DemoName : 336840328 // 缓存清空后重新获取