当前位置: 首页 > news >正文

自定义注解 + AOP:打造企业级通用组件(日志、限流、幂等)

在企业的复杂业务系统中,我们经常会遇到这样的困境:
业务代码里充斥着大量的“非业务逻辑”——
“先记一下日志...”
“查一下 Redis 看有没有重复提交...”
“算一下这个 IP 这一秒请求了几次...”

这些代码像牛皮藓一样贴在核心业务逻辑上,让代码变得臃肿、难以维护。
这就好比:你只是想请 VIP 客户进门喝茶(核心业务),结果让他自己在门口填表(日志)、自己掏钥匙开门(鉴权)、自己排队领号(限流)。太累了!

今天,我们要用自定义注解 + AOP的组合拳,打造一套“实战档次的基础设施”
我们将业务代码中的杂活剥离出来,封装成通用的组件。
从此以后,业务开发只需要关注核心逻辑,剩下的脏活累活,交给“安检员”(AOP 切面)去处理。

核心隐喻:特殊标记与安检员

  • 自定义注解 = “特殊标记”
    你在方法上贴个@OperLog,就像在门口挂了个“VIP 通道”的牌子。
    你在方法上贴个@Idempotent,就像挂了个“防弹玻璃”的牌子。

  • AOP 切面 = “全能安检员”
    安检员站在门口(拦截器),盯着每一个进来的人(请求)。
    他不需要认识具体的人,只需要看牌子:

    • 看到“VIP 通道”?->自动记录是谁进来了(日志)。
    • 看到“防弹玻璃”?->检查这人是不是刚才那个捣乱的(幂等性)。
    • 看到“限流闸机”?->掐表看这人是不是来得太频繁(限流)。

核心价值解耦。业务代码只负责“喝茶”,安检员负责“安保”。

实战案例 1:通用日志切面 —— “全链路监控眼”

痛点:每个接口都要记录“谁、在什么时间、调用了什么方法、传了什么参数、花了多久、结果如何”。如果手写,代码量巨大。

1. 定义“标记”:@OperLog

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface OperLog { // 操作描述,例如:“新增用户” String value() default ""; // 操作类型:0=其他 1=新增 2=修改 3=删除 int type() default 0; }

2. 部署“安检员”:LogAspect

这里我们利用@Around环绕通知,因为它能拿到执行前(参数)和执行后(返回值、耗时)的所有信息

@Aspect @Component @Slf4j public class LogAspect { // 切入点:所有标了 @OperLog 的方法 @Around("@annotation(operLog)") public Object doAround(ProceedingJoinPoint joinPoint, OperLog operLog) throws Throwable { long startTime = System.currentTimeMillis(); // 1. 获取请求参数 Object[] args = joinPoint.getArgs(); // 这里可以序列化参数,注意:大文件参数要过滤,防止 OOM String params = JSON.toJSONString(args); Object result = null; try { // 2. 【核心】执行目标方法 result = joinPoint.proceed(); return result; } catch (Exception e) { log.error("业务异常: {}", e.getMessage()); throw e; // 必须抛出,否则业务层捕获不到异常 } finally { // 3. 记录耗时和结果 long endTime = System.currentTimeMillis(); long cost = endTime - startTime; // 4. 异步保存日志 (关键!不要阻塞主线程) // 使用 Spring 的 @Async 或者线程池 saveLogAsync(operLog, params, result, cost); } } private void saveLogAsync(OperLog operLog, String params, Object result, long cost) { // 模拟异步入库 System.out.println(String.format("【日志入库】操作:%s, 参数:%s, 耗时:%dms, 结果:%s", operLog.value(), params, cost, JSON.toJSONString(result))); } }
  • 异步是关键:日志是辅助功能,绝对不能因为写日志慢而拖垮业务接口。
  • 参数脱敏:在序列化args时,要注意过滤掉密码、文件流等敏感或超大字段。

实战案例 2:幂等性/防重提交切面 —— “防弹护盾”

痛点:用户手抖点了两次提交按钮,或者网络卡顿导致重试,结果产生了两条一样的订单。

1. 定义“标记”:@Idempotent

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Idempotent { // 唯一键前缀,例如 "order:submit" String key(); // 锁定时间(秒),默认 5 秒 int expire() default 5; }

2. 部署“安检员”:IdempotentAspect

这里利用 Redis 的setIfAbsent(SETNX) 来实现分布式锁。

@Aspect @Component public class IdempotentAspect { @Autowired private RedisTemplate<String, Object> redisTemplate; @Around("@annotation(idempotent)") public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable { // 1. 生成 Redis Key // 策略:Key = 前缀 + 用户ID (防止不同用户互斥) + 方法签名 String userId = UserContext.getCurrentUserId(); // 假设从上下文获取 String key = idempotent.key() + ":" + userId; // 2. 尝试加锁 (SETNX) // 如果返回 true,说明拿到了锁(第一次请求) // 如果返回 false,说明锁已存在(重复请求) Boolean isAbsent = redisTemplate.opsForValue() .setIfAbsent(key, "LOCK", idempotent.expire(), TimeUnit.SECONDS); if (Boolean.FALSE.equals(isAbsent)) { // 3. 拦截:拒绝重复请求 throw new BusinessException("请勿重复提交,请稍后再试"); } try { // 4. 放行:执行业务 return joinPoint.proceed(); } finally { // 5. 释放锁 (可选) // 如果 expire 时间足够覆盖业务执行时间,也可以不手动删,让它自动过期 // 但为了严谨,通常建议在 finally 中删除 redisTemplate.delete(key); } } }
  • Key 的设计:只锁用户 ID 可能太宽泛,可以加上方法名或业务参数(如订单号)。
  • Lua 脚本:在高并发下,为了保证“判断+设置”的原子性,最好使用 Lua 脚本替代setIfAbsent

实战案例 3:动态限流切面 —— “流量调节阀”

痛点:某个接口突然被刷,数据库 CPU 飙升。我们需要在代码层面加一道防线。

1. 定义“标记”:@RateLimit

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimit { // 限流 Key(用于区分不同接口) String key(); // 最大请求数 int maxRequests(); // 时间窗口(秒) int timeout(); }

2. 部署“安检员”:RateLimitAspect

这里演示两种流派:Guava 单机限流Redis 分布式限流

流派 A:单机限流 (Guava RateLimiter)

适合单体应用,利用令牌桶算法。

@Aspect @Component public class RateLimitAspect { // 缓存限流器,Key -> RateLimiter private final Map<String, RateLimiter> limiterMap = new ConcurrentHashMap<>(); @Around("@annotation(rateLimit)") public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable { String key = rateLimit.key(); // 获取或创建限流器 (每秒 maxRequests 个令牌) RateLimiter limiter = limiterMap.computeIfAbsent(key, k -> RateLimiter.create(rateLimit.maxRequests())); // 尝试获取令牌,超时 0.5 秒 if (!limiter.tryAcquire(500, TimeUnit.MILLISECONDS)) { throw new TooManyRequestsException("系统繁忙,请稍后再试"); } return joinPoint.proceed(); } }
流派 B:分布式限流 (Redis + Lua)

适合集群部署,所有机器共享一个计数器。

// 伪代码逻辑 String script = "local key = KEYS[1] " + "local limit = tonumber(ARGV[1]) " + "local current = tonumber(redis.call('get', key) or '0') " + "if current + 1 > limit then " + " return 0 " + "else " + " return redis.call('incr', key) " + "end"; // 在切面中执行 script Long count = redisTemplate.execute(script, Collections.singletonList(key), limit); if (count == 0) { throw new TooManyRequestsException("流量过大"); }
  • Guava简单高效,但集群环境下限流总数会翻倍(10 台机器 * 100 QPS = 1000 QPS)。
  • Redis精确,但增加了网络 IO 开销。

从“功能”到“基础设施”

通过这三个案例,我们可以看到自定义注解 + AOP 的强大之处:

  1. 零侵入:业务代码里只有@OperLog@Idempotent,干干净净。
  2. 可复用:写一次切面,全公司 100 个微服务都能用。
  3. 易维护:修改限流算法?改切面代码就行,不用动业务逻辑。

最后,送上金句

“优秀的架构师善于利用 AOP 将非业务逻辑封装成‘基础设施’。业务开发人员只需要关注核心逻辑(怎么赚钱),剩下的脏活累活(怎么记账、怎么安保),交给注解和切面。”

现在,去给你的项目贴上“标记”,部署“安检员”吧!️

http://www.jsqmd.com/news/546275/

相关文章:

  • ABC系统实战指南:逻辑综合与形式验证的数字电路设计工具
  • WordPress插件开发避坑指南:从CVE-2025-4334看如何正确设计用户注册与权限验证
  • OpenClaw技能组合:Qwen3.5-9B实现会议纪要自动生成与待办同步
  • 深入解析卷积层参数量与FLOPs的计算原理及优化策略
  • 告别环境依赖:给你的PyTorch模型加载代码加上‘设备自适应’的健壮性设计
  • Vscode配置C++多文件编译的完整指南(含常见错误排查)
  • 从0到1搞懂AI智能体:小白也能轻松入门的完整技术路线图!
  • Go语言中的Slice:性能优化技巧
  • 根据您提供的写作范围,我为您总结的标题为:“昆通泰MCGS7.7嵌入版:6车位停车场监控系统仿...
  • PVEL-AD:突破性光伏电池缺陷检测数据集的技术解析与研究价值
  • 抖音批量下载终极指南:免费无水印视频一键获取
  • 颠覆式数据可视化创作:Charticulator让每个人都能成为数据艺术家
  • MobaXterm功能解锁工具:从授权到企业部署的完整指南
  • 别再死记硬背了!用Python脚本+Modbus Poll工具,5分钟搞懂Modbus功能码怎么用
  • 整理网络相关零散笔记 - wanghongwei
  • 从零开始:OWASP TOP10漏洞详解与渗透测试入门教程
  • 企业人力资源系统怎么选,AI能力是关键考量
  • SubtitleOCR:重新定义视频内容处理效率的硬字幕提取革命
  • ESP32-S3实战:LVGL图形库与ST7789V屏幕的深度适配指南
  • Java线程池工作原理与回收机制
  • 2026年 GEO优化推广运营厂家推荐榜单:AI获客与搜索推广,专业实力与市场口碑深度解析 - 品牌企业推荐师(官方)
  • 最近刚啃完一个电-气综合能源系统耦合优化调度的活,算是把之前一直想搞的电网和气网联动调度给跑通了
  • 如何快速掌握Spring框架:面向初学者的完整指南
  • 工作流介绍
  • 3个核心功能如何解决手游玩家的日常任务负担
  • 计算机毕业设计springboot重修课程信息管理系统 基于SpringBoot的高校补考重修教务管理平台设计与实现 大学课程重修申请与成绩管理信息系统构建研究
  • H3C 交换机SSH安全登录配置详解
  • SVGnest智能嵌套算法架构解析:工业级材料利用率优化实战指南
  • ConvNeXt 改进 :ConvNeXt添加KANConv卷积(有九种不同类型激活函数,KAN卷积一夜干掉MLP,2024),二次创新CNBlock结构
  • 探索分子世界的三维画笔:PyMOL开源版如何让你成为分子艺术家?