AOP 实现 Redis 缓存切面解析

AOP 实现 Redis 缓存切面解析

这段代码是一个基于 Spring AOP + Redis 的通用缓存切面实现,通过自定义 @Cache 注解实现方法级别的缓存读写,减少重复数据库查询。


一、核心结构与依赖

1. 切面基础定义

java

运行

复制代码
@Aspect
@Component
@Slf4j
public class CacheAspect {
    private static final ObjectMapper objectMapper = new ObjectMapper();
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Pointcut("@annotation(com.mszlu.blog.common.cache.Cache)")
    public void pt(){}
}
  • @Aspect:标记为切面类,定义切点与通知逻辑
  • @Component:交给 Spring 管理,切面才能生效
  • @Slf4j:Lombok 日志注解,简化日志输出
  • @Pointcut:切点表达式,匹配所有标注@Cache注解的方法

二、核心流程:环绕通知(@Around)

1. 步骤拆解

① 获取方法信息与参数

java

运行

复制代码
Signature signature = pjp.getSignature();
String className = pjp.getTarget().getClass().getSimpleName();
String methodName = signature.getName();
Class[] parameterTypes = new Class[pjp.getArgs().length];
Object[] args = pjp.getArgs();
String params = "";
for(int i=0; i<args.length; i++) {
    if(args[i] != null) {
        params += JSON.toJSONString(args[i]);
        parameterTypes[i] = args[i].getClass();
    } else {
        parameterTypes[i] = null;
    }
}
// 参数MD5加密,避免key过长
if (StringUtils.isNotEmpty(params)) {
    params = DigestUtils.md5Hex(params);
}
  • ProceedingJoinPoint获取目标类名、方法名、参数列表
  • 参数转为 JSON 字符串后,用 MD5 加密生成唯一标识,避免 Redis key 过长
  • 后续用class+method+params拼接 Redis key,保证不同方法 / 参数的缓存隔离
② 获取@Cache注解配置

java

运行

复制代码
Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes);
Cache annotation = method.getAnnotation(Cache.class);
long expire = annotation.expire();
String name = annotation.name();
  • 通过反射获取方法上的@Cache注解,读取name(缓存前缀)和expire(过期时间)
③ 拼接 Redis Key

java

运行

复制代码
String redisKey = name + "::" + className + "::" + methodName + "::" + params;

Key 格式:缓存前缀::类名::方法名::参数MD5值,确保唯一性

④ 缓存读取(命中则直接返回)

java

运行

复制代码
String redisValue = redisTemplate.opsForValue().get(redisKey);
if (StringUtils.isNotEmpty(redisValue)) {
    log.info("走了缓存~~~,{}", redisKey);
    Result result = JSON.parseObject(redisValue, Result.class);
    return result;
}
  • 从 Redis 查询 key,若存在则直接解析 JSON 为Result对象并返回
  • 跳过目标方法执行,减少 DB 查询
⑤ 目标方法执行 + 缓存写入

java

运行

复制代码
Object proceed = pjp.proceed();
redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(proceed), Duration.ofMillis(expire));
log.info("存入缓存~~~ {},{}", className, methodName);
return proceed;
  • 调用pjp.proceed()执行原方法,获取返回结果
  • 将结果转为 JSON 字符串,存入 Redis 并设置过期时间
  • 返回原方法结果,后续相同请求会直接命中缓存
⑥ 异常处理

java

运行

复制代码
catch (Throwable throwable) {
    throwable.printStackTrace();
    return Result.fail(code: -999, msg: "系统错误");
}
  • 捕获所有异常,打印栈信息并返回统一错误结果,避免因缓存逻辑异常导致接口直接报错

三、关键优化与设计亮点

  1. 通用缓存 Key 设计name+className+methodName+params拼接 Key,支持多模块、多方法的缓存隔离,参数 MD5 加密避免 key 过长和特殊字符问题。
  2. 无侵入式缓存 业务方法仅需加@Cache(expire=xxx, name="xxx")注解,无需修改业务代码,解耦缓存逻辑。
  3. 缓存过期控制 注解中配置的expire参数直接控制 Redis 过期时间,支持不同方法设置不同缓存有效期。
  4. 异常降级缓存逻辑异常时,捕获错误并返回友好提示,不影响核心业务(也可改为直接执行原方法,降级为 "缓存失效")。

四、可优化点(面试 / 项目拓展)

  1. 参数类型获取问题 当前用getMethod(methodName, parameterTypes)获取方法时,若参数为 null,parameterTypes[i] = null会导致反射报错。优化方案:用org.aspectj.lang.reflect.MethodSignature直接获取方法对象:

    java

    运行

    复制代码
    MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
    Method method = methodSignature.getMethod();
  2. 空值缓存防穿透 若 DB 查询结果为 null,当前逻辑不会写入缓存,导致缓存穿透。可添加空值缓存(设置较短过期时间):

    java

    运行

    复制代码
    if (proceed == null) {
        redisTemplate.opsForValue().set(redisKey, "null", Duration.ofMillis(30000));
    }
  3. 缓存击穿 / 雪崩优化 可添加本地锁(如synchronized)或分布式锁(Redis SETNX),防止热点 key 过期时大量请求打向 DB。

  4. 序列化统一 直接用JSON.toJSONString序列化,建议配置RedisTemplate的序列化器(如 Jackson2JsonRedisSerializer),保证序列化一致性。


五、使用示例

在业务方法上添加@Cache注解即可生效:

java

运行

复制代码
@Cache(name = "article", expire = 600000) // 缓存10分钟
public Result listArticle(PageParams pageParams) {
    // 原业务代码(如DB查询)
    return Result.success(records);
}

调用时,第一次请求执行原方法并写入 Redis,后续相同参数请求直接返回缓存结果。

相关推荐
库拉大叔4 小时前
工具调用效率对比实测:GPT-5.5与Gemini 3.5 Flash性能评估
java·前端·人工智能
我是唐青枫4 小时前
Java MyBatis 实战指南:XML 映射、动态 SQL 与数据访问层设计
java·mybatis
fanjiu20204 小时前
python查询nightingale监控
python
摇滚侠4 小时前
Spring 零基础入门到进阶 面向切面 AOP 52-60
java·后端·spring
feifeigo1234 小时前
马尔可夫决策过程(MDP)MATLAB 实现
开发语言·matlab
就改了4 小时前
微服务接口性能优化:CompletableFuture 并行聚合实践
java·微服务·性能优化
攻城狮Soar4 小时前
STL源码解析之list(1)
开发语言·c++
TechWayfarer4 小时前
IP画像在企业安全中的应用:它能做什么?不能替代什么
网络·python·tcp/ip·安全·网络安全
x***r1514 小时前
Postman-win64-7.3.5-Setup安装配置教程(Windows 详细版)
开发语言·lua