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

Java 注解式限流教程(使用 Redis + AOP)

在这里插入图片描述

Java 注解式限流教程(使用 Redis + AOP)

在上一节中,我们已经实现了基于 Redis 的请求频率控制。现在我们将进一步升级功能,使用 Spring AOP + 自定义注解 实现一个更优雅、可复用的限流方式 —— 即通过 @RateLimiter 注解,对任意接口进行限流保护。


🧩 技术栈

  • Spring Boot 3.x
  • Redis
  • Jedis 连接池
  • Lua 脚本实现原子性操作
  • Spring AOP 实现注解式切面处理

📦 Maven 依赖配置(补充 AOP 支持)

确保你的 pom.xml 中包含以下依赖:

<!-- Spring Boot Redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>3.2.10</version>
</dependency><!-- Jedis 连接池 -->
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>5.0.2</version>
</dependency><!-- Spring Boot AOP -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>3.2.10</version>
</dependency>

🛠️ Redis 配置(application.yml)

与之前保持一致:

spring:data:redis:host: 127.0.0.1port: 6379password: database: 1timeout: 5000msjedis:pool:max-active: 8   # 最大连接数max-idle: 4     # 最大空闲连接min-idle: 1     # 最小空闲连接max-wait: 2000ms # 获取连接最大等待时间

📁 项目结构概览

org.example.websocket.test
├── annotation
│   └── RateLimiter.java
├── aspect
│   └── RateLimiterAspect.java
├── utils
│   └── RedisRateLimiter.java
├── config
│   └── RateLimiterConfig.java
└── controller└── RateLimitController.java

🔖 第一步:创建自定义限流注解

RateLimiter.java

package org.example.websocket.test.annotation;import java.lang.annotation.*;/*** 自定义限流注解,支持方法或类级别标注*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter {/*** 每个窗口内允许的最大请求数,默认10次*/int limit() default 10;/*** 窗口时间(秒),默认60秒*/int windowTime() default 60;/*** 限流维度:* - ip: 按客户端IP限流* - user: 按用户ID限流(需从参数中获取)*/String key() default "ip";
}

🧠 第二步:编写 AOP 切面逻辑

RateLimiterAspect.java

package org.example.websocket.test.aspect;import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.example.websocket.test.annotation.RateLimiter;
import org.example.websocket.test.utils.RedisRateLimiter;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import java.util.Random;@Aspect
@Component
public class RateLimiterAspect {private final RedisRateLimiter redisRateLimiter;public RateLimiterAspect(RedisRateLimiter redisRateLimiter) {this.redisRateLimiter = redisRateLimiter;}@Around("@annotation(rateLimiter)")public Object around(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String keyPrefix = rateLimiter.key();String dynamicKey = "";if ("ip".equals(keyPrefix)) {dynamicKey = getClientIP(request);} else if ("user".equals(keyPrefix)) {dynamicKey = request.getParameter("account");}if (dynamicKey == null || dynamicKey.isEmpty()) {return "无法识别限流标识,请检查请求参数或IP信息";}String redisKey = "rate_limit:" + keyPrefix + ":" + dynamicKey;// 执行限流判断if (!redisRateLimiter.check(redisKey, rateLimiter.limit(), rateLimiter.windowTime())) {return "请求过于频繁,请稍后再试";}// 防枚举攻击延迟try {Thread.sleep(new Random().nextInt(200));} catch (InterruptedException ignored) {}// 继续执行原方法return joinPoint.proceed();}private String getClientIP(HttpServletRequest request) {String ip = request.getHeader("X-Forwarded-For");if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}if (ip != null && ip.contains(",")) {ip = ip.split(",")[0].trim();}return ip;}
}

🧮 第三步:优化 Redis 限流工具类

RedisRateLimiter.java

package org.example.websocket.test.utils;import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Collections;public class RedisRateLimiter {private final StringRedisTemplate redisTemplate;public RedisRateLimiter(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}public boolean check(String key, int limit, int expireTime) {String luaScript = buildLuaScript();DefaultRedisScript<Boolean> script = new DefaultRedisScript<>(luaScript, Boolean.class);Boolean isAllowed = redisTemplate.execute(script,Collections.singletonList(key),String.valueOf(limit), String.valueOf(expireTime));return Boolean.TRUE.equals(isAllowed);}private String buildLuaScript() {return "local key = KEYS[1]\n" +"local limit = tonumber(ARGV[1])\n" +"local expire_time = tonumber(ARGV[2])\n" +"local current = redis.call('incr', key)\n" +"if current == 1 then\n" +"    redis.call('expire', key, expire_time)\n" +"elseif current > limit then\n" +"    return false\n" +"end\n" +"return true";}
}

⚙️ 第四步:注册 RedisRateLimiter Bean

RateLimiterConfig.java

package org.example.websocket.test.config;import org.example.websocket.test.utils.RedisRateLimiter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;@Configuration
public class RateLimiterConfig {@Beanpublic RedisRateLimiter redisRateLimiter(StringRedisTemplate redisTemplate) {return new RedisRateLimiter(redisTemplate);}
}

🧪 第五步:在控制器中使用注解限流

RateLimitController.java

package org.example.websocket.test.controller;import jakarta.servlet.http.HttpServletRequest;
import org.example.websocket.test.annotation.RateLimiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class RateLimitController {// 使用注解按 IP 限流@GetMapping("/testAnnoRateLimit")@RateLimiter(limit = 10, windowTime = 60, key = "ip")public String testAnnoRateLimit(@RequestParam String account, HttpServletRequest request) {return "恭喜,请求成功放行!";}// 使用注解按用户限流@GetMapping("/testUserRateLimit")@RateLimiter(limit = 5, windowTime = 30, key = "user")public String testUserRateLimit(@RequestParam String account, HttpServletRequest request) {return "用户[" + account + "] 请求成功放行!";}
}

✅ 效果演示

当你多次快速访问 /testAnnoRateLimit 接口时,在超过设定频率后会返回:

请求过于频繁,请稍后再试

📌 总结

你现在拥有了一个 基于注解的限流系统,可以轻松地对任意接口进行限流保护。该方案具备如下优点:

特性描述
✅ 注解驱动使用 @RateLimiter 轻松启用限流
✅ 多种限流维度支持 IP、用户等多维限流
✅ Lua 原子操作Redis + Lua 保证线程安全
✅ 易于扩展后续可增加令牌桶算法、滑动窗口优化等

http://www.xdnf.cn/news/10221.html

相关文章:

  • 【速写】PPOTrainer样例与错误思考(少量DAPO)
  • Java线程安全解决方案全面指南
  • day024-网络基础-TCP与UDP、DNS
  • 网络编程总结
  • 深度学习|pytorch基本运算
  • Spring Cloud 学习 —— 简单了解
  • 若依项目天气模块
  • 国产高安全芯片在供应链自主可控中的综合优势与案例分析
  • 【MySQL】MVCC与Read View
  • AWS WebRTC:获取ICE服务地址(part 3):STUN服务和TURN服务的作用
  • 影刀RPA元素稳定性优化:多策略应对失效难题
  • 带你手写React中的useReducer函数。(底层实现)
  • OpenCV中的分水岭算法 (C/C++)
  • 聊聊前端工程化
  • C#上传图片后压缩
  • 【Dify学习笔记】:Dify离线安装插件教程
  • 【原理扫描】不安全的crossdomain.xml文件和CORS(跨站资源共享)原始验证失败验证与彻底方案
  • (24)多租户 SaaS 平台设计
  • C语言进阶--自定义类型详解(结构体、枚举、联合)
  • AWS WAF设置IP白名单
  • 指数函数的泰勒展开可视化:从数学理论到Python实现
  • 历年西北工业大学计算机保研上机真题
  • 【已解决】YFRateLimitError(‘Too Many Requests. Rate limited. Try after a while.‘)
  • Spring Boot 3 整合 MQ 构建聊天消息存储系统
  • 测试用例及黑盒测试方法
  • Java进化之路:从Java 8到Java 21的重要新特性(深度解析)
  • JS手写代码篇---手写节流函数
  • Linux(8)——进程(控制篇——上)
  • mac mini m4命令行管理员密码设置
  • 【Java基础-环境搭建-创建项目】IntelliJ IDEA创建Java项目的详细步骤