在一些业务场景下,我们可以把redis的缓存值通过注解声明在方法上,避免与业务代码耦合在一起,减少代码的侵入性。
- 定义实现缓存的注解
/**
* Created with IntelliJ IDEA.
* @Author: zhaoxn
* @Date: 2022/11/03/22:35
* @Description:自定义注解实现缓存注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheData {
String prefix(); //key的前缀
String key(); //spel表达式
long expireSecond() default 300; //过期时间 默认300s
}
- 切面逻辑处理
/**
* Created with IntelliJ IDEA.
*
* @Author: zhaoxn
* @Date: 2022/11/03/21:49
* @Description:
*/
@Aspect
@Component
@Slf4j
public class CacheDataAspect {
@Autowired
private RedisTemplate redisTemplate;
private SpelExpressionParser parser = new SpelExpressionParser();
private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Around("@annotation(cacheData)")
public Object cacheDataAround(ProceedingJoinPoint pjp,CacheData cacheData) throws Throwable {
//前缀
String prefix = cacheData.prefix();
//key
String key = cacheData.key();
//过期时间
long second = cacheData.expireSecond();
return save(prefix,key,second,pjp);
}
private Object save(String prefix, String key, long second, ProceedingJoinPoint pjp) throws Throwable {
Method method = getMethod(pjp);
//构建redis key
String redisKey = prefix + parserSpel(method,pjp.getArgs(),key);
Object data = redisTemplate.opsForValue().get(key);
//判断redis缓存中key是否过期 过期重新赋值
if(ObjectUtils.isEmpty(data)){
Object proceed = pjp.proceed();
redisTemplate.opsForValue().set(redisKey,proceed,second, TimeUnit.SECONDS);
return proceed;
}
return data;
}
//spel表达式 获取key值
private Object parserSpel(Method method, Object[] args,String spel) {
//对应方法形参
String[] params = discoverer.getParameterNames(method);
if(params == null){
log.error("方法{}参数为空",method);
}
//spel解析
StandardEvaluationContext context = new StandardEvaluationContext();
for (int i = 0;i<params.length;i++){
context.setVariable(params[i],args[i]);
}
//spel表达式 获取对应字段的值
try{
Expression expression = parser.parseExpression(spel);
return expression.getValue(context);
}catch (Exception e){
log.error("",e);
throw new RuntimeException();
}
}
//获取method对象
private Method getMethod(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
if(method.getDeclaringClass().isInterface()){
try{
method = pjp.getTarget().getClass().getDeclaredMethod(pjp.getSignature().getName(),method.getParameterTypes());
}catch (Exception e){
log.error("",e);
}
}
return method;
}
}
- 使用样例:
@CacheData(prefix = "Application:Module:Business:",key = "#person.data",expireSecond = 800)
public String testCache(Person person){
String name = person.getData();
System.out.println(".............业务逻辑");
return "data";
}
使用方式:存放redis的key,是由CacheData注解的prefix+key组成。redis的value存放是该方法的返回结果。
CacheData注解:
prefix:拼接的前缀(参考上图)
key:获取方法上形参的值,如果为String类型,格式为:“#形参名”。如果为对象,格式“#对象名.对象属性名”
expireSecond:过期时间,单位为秒,默认300s。
顺道说下 redis的key 推荐命名规范
以服务名 业务模块 具体功能 作为前缀中间以冒号进行分隔
如:AppUser : Risk : repeatAttack : 1880494XXXX
这样的好处就是在可视化工具下key会以层级目录的形式展示