You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 22:13:14