Spring Boot中如何从配置文件批量创建RedisRateLimiter Bean列表?
问题解决:Spring Cloud Gateway 批量创建RedisRateLimiter Bean及动态限流方案
一、为什么直接返回List会报错?
RedisRateLimiter实现了InitializingBean接口,它的核心初始化逻辑(比如加载Redis限流脚本、初始化Redis模板)都在afterPropertiesSet()方法中。当你手动new实例并包装成List返回时,这些实例并未被Spring容器管理,afterPropertiesSet()不会被调用,导致调用isAllowed()时抛出未初始化异常。
二、正确批量创建RedisRateLimiter Bean的方法
通过BeanDefinitionRegistryPostProcessor动态向Spring容器注册每个RedisRateLimiter的Bean定义,让Spring负责实例化和初始化流程。
步骤1:配置属性绑定类
@ConfigurationProperties(prefix = "rate-limit") public class RateLimitProperties { private List<RateLimitItem> properties; // Getters & Setters public static class RateLimitItem { private int rate; // 如果需要burstCapacity可以添加对应字段 // Getters & Setters } }
步骤2:实现BeanDefinitionRegistryPostProcessor
@Component public class RedisRateLimiterRegistrar implements BeanDefinitionRegistryPostProcessor { private final RateLimitProperties rateLimitProperties; public RedisRateLimiterRegistrar(RateLimitProperties rateLimitProperties) { this.rateLimitProperties = rateLimitProperties; } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { List<RateLimitProperties.RateLimitItem> items = rateLimitProperties.getProperties(); for (int i = 0; i < items.size(); i++) { RateLimitProperties.RateLimitItem item = items.get(i); // 创建RedisRateLimiter的Bean定义 GenericBeanDefinition beanDef = new GenericBeanDefinition(); beanDef.setBeanClass(RedisRateLimiter.class); // 设置构造参数(根据RedisRateLimiter的实际构造方法调整) ConstructorArgumentValues args = new ConstructorArgumentValues(); args.addIndexedArgumentValue(0, item.getRate()); // replenishRate args.addIndexedArgumentValue(1, item.getRate()); // burstCapacity(可从配置中扩展) beanDef.setConstructorArgumentValues(args); // 注册Bean,命名为redisRateLimiter0、redisRateLimiter1... registry.registerBeanDefinition("redisRateLimiter" + i, beanDef); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 无需额外处理 } }
步骤3:启用配置绑定
在启动类上添加注解:
@SpringBootApplication @EnableConfigurationProperties(RateLimitProperties.class) public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
这样每个RedisRateLimiter都会被Spring容器管理,初始化方法自动执行,调用isAllowed()时就不会报错。
三、不同URL设置不同限流规则的更优思路
批量创建Bean的方式虽然可行,但维护成本高(每个规则对应一个Bean)。更推荐基于请求属性动态匹配限流规则的方案,只需一个自定义RateLimiter即可处理所有场景:
1. 调整配置结构
rate-limit: url-rules: "/api/v1/**": rate: 2 burst: 4 "/api/v2/**": rate: 5 burst: 10
2. 自定义URL匹配的RedisRateLimiter
public class UrlBasedRedisRateLimiter extends RedisRateLimiter { private final Map<String, RateRule> urlRuleMap; private final PathMatcher pathMatcher; public UrlBasedRedisRateLimiter(RedisTemplate<String, String> redisTemplate, ReactiveRedisTemplate<String, String> reactiveRedisTemplate, RateLimitProperties rateLimitProperties) { super(redisTemplate, reactiveRedisTemplate); // 加载URL规则映射 this.urlRuleMap = rateLimitProperties.getUrlRules().entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, entry -> { RateRule rule = new RateRule(); rule.setRate(entry.getValue().getRate()); rule.setBurst(entry.getValue().getBurst()); return rule; })); this.pathMatcher = new AntPathMatcher(); } // 自定义方法,接收请求路径参数 public Mono<Response> isAllowed(String routeId, String userId, String requestPath) { // 匹配对应的限流规则 RateRule rule = urlRuleMap.entrySet().stream() .filter(entry -> pathMatcher.match(entry.getKey(), requestPath)) .findFirst() .map(Map.Entry::getValue) .orElseThrow(() -> new IllegalArgumentException("No rate limit rule found for path: " + requestPath)); // 调用父类核心逻辑,传入当前规则的参数 return getRedisScript().execute( getReactiveRedisTemplate(), getKeys(userId), rule.getRate(), rule.getBurst(), Instant.now().getEpochSecond() ).map(response -> { boolean allowed = response.get(0) == 1L; Long tokensLeft = response.get(1); return new Response(allowed, getHeaders(rule.getRate(), rule.getBurst(), tokensLeft)); }); } private static class RateRule { private int rate; private int burst; // Getters & Setters } }
3. 自定义GatewayFilterFactory
@Component public class UrlRateLimitGatewayFilterFactory extends AbstractGatewayFilterFactory<UrlRateLimitGatewayFilterFactory.Config> { private final UrlBasedRedisRateLimiter rateLimiter; public UrlRateLimitGatewayFilterFactory(UrlBasedRedisRateLimiter rateLimiter) { super(Config.class); this.rateLimiter = rateLimiter; } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { String requestPath = exchange.getRequest().getPath().value(); String routeId = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR).getId(); // 自定义获取标识ID(可以是用户ID、IP等) String identifier = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress(); return rateLimiter.isAllowed(routeId, identifier, requestPath) .flatMap(response -> { if (response.isAllowed()) { return chain.filter(exchange); } else { exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); return exchange.getResponse().setComplete(); } }); }; } public static class Config { // 可扩展配置参数,比如是否启用限流 } }
4. 路由中使用自定义Filter
spring: cloud: gateway: routes: - id: api_v1 uri: http://localhost:8081 predicates: - Path=/api/v1/** filters: - UrlRateLimit - id: api_v2 uri: http://localhost:8082 predicates: - Path=/api/v2/** filters: - UrlRateLimit
这种方案无需维护多个RedisRateLimiter Bean,配置更灵活,新增或修改规则只需调整yaml即可。
内容的提问来源于stack exchange,提问作者programmer
相关产品推荐
相关产品推荐

