Build a Switchable SpringBoot Rate Limiter with Guava and Redis

This tutorial walks through creating a reusable SpringBoot starter that combines Guava's local token‑bucket limiter and Redis's distributed limiter, allowing developers to toggle between them via a simple configuration property and integrate the solution seamlessly with AOP and conditional beans.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Build a Switchable SpringBoot Rate Limiter with Guava and Redis

Earlier articles introduced using Guava and Redis for rate limiting in SpringBoot. This guide shows how to merge both mechanisms into a single, configurable component that can be switched at runtime.

Step 1 – Create a common module cloud-limiter-starter

Add the required dependencies to the module’s pom.xml:

<dependencies>
  <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
  </dependency>
  <!--SpringFramework-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <scope>provided</scope>
  </dependency>
</dependencies>
Tip: Name the starter module with the starter suffix and set the dependencies’ scope to provided .

Step 2 – Implement the rate‑limiting logic

Create a common interface:

public interface LimiterManager {
    boolean tryAccess(Limiter limiter);
}

Guava implementation ( GuavaLimiter)

@Slf4j
public class GuavaLimiter implements LimiterManager {
    private final Map<String, RateLimiter> limiterMap = Maps.newConcurrentMap();

    @Override
    public boolean tryAccess(Limiter limiter) {
        RateLimiter rateLimiter = getRateLimiter(limiter);
        if (rateLimiter == null) {
            return false;
        }
        boolean access = rateLimiter.tryAcquire(1, 100, TimeUnit.MILLISECONDS);
        log.info("{} access :{}", limiter.getKey(), access);
        return access;
    }
}

Redis implementation ( RedisLimiter)

@Slf4j
public class RedisLimiter implements LimiterManager {
    private final StringRedisTemplate stringRedisTemplate;

    public RedisLimiter(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean tryAccess(Limiter limiter) {
        String key = limiter.getKey();
        if (StringUtils.isEmpty(key)) {
            throw new LimiterException("redis limiter key cannot be null");
        }
        List<String> keys = new ArrayList<>();
        keys.add(key);
        int seconds = limiter.getSeconds();
        int limitCount = limiter.getLimitNum();
        String luaScript = buildLuaScript();
        RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
        Long count = stringRedisTemplate.execute(redisScript, keys, "" + limitCount, "" + seconds);
        log.info("Access try count is {} for key={}", count, key);
        return count != null && count != 0;
    }
}

Step 3 – Create a configuration class

@Configuration
public class LimiterConfigure {

    @Bean
    @ConditionalOnProperty(name = "limit.type", havingValue = "local")
    public LimiterManager guavaLimiter() {
        return new GuavaLimiter();
    }

    @Bean
    @ConditionalOnProperty(name = "limit.type", havingValue = "redis")
    public LimiterManager redisLimiter(StringRedisTemplate stringRedisTemplate) {
        return new RedisLimiter(stringRedisTemplate);
    }
}

Step 4 – Implement the AOP aspect

@Aspect
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Conditional(LimitAspectCondition.class)
public class LimitAspect {

    @Setter(onMethod_ = @Autowired)
    private LimiterManager limiterManager;

    @Pointcut("@annotation(com.jianzh5.limit.aop.Limit)")
    private void check() {}

    @Before("check()")
    public void before(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Limit limit = method.getAnnotation(Limit.class);
        if (limit != null) {
            Limiter limiter = Limiter.builder()
                    .limitNum(limit.limitNum())
                    .seconds(limit.seconds())
                    .key(limit.key())
                    .build();
            if (!limiterManager.tryAccess(limiter)) {
                throw new LimiterException("There are currently many people, please try again later!");
            }
        }
    }
}

The aspect is only loaded when the configuration property limit.type exists, thanks to the custom condition:

public class LimitAspectCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Check if the environment contains the property
        return context.getEnvironment().containsProperty(ConfigConstant.LIMIT_TYPE);
    }
}

Step 5 – Register the auto‑configuration

## AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.jianzh5.limit.config.LimiterConfigure,\
  com.jianzh5.limit.aop.LimitAspect

Directory structure (illustrative):

Step 6 – Use the limiter in a project

Add the starter dependency:

<dependency>
    <groupId>com.jianzh5</groupId>
    <artifactId>cloud-limit-starter</artifactId>
</dependency>

Configure the desired limiter in application.properties: limit.type=redis Annotate the target API method:

@Limit(key = "Limiter:test", limitNum = 3, seconds = 1)

When the property is omitted, no limiter is loaded.

Conclusion

By following these steps you obtain a flexible, plug‑and‑play rate‑limiting starter for SpringBoot. Switching between local Guava and distributed Redis implementations is as easy as changing a configuration value, keeping business code clean and maintainable.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaaopredisGuavaSpringBootStarter
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.