Understanding policy providers
TheRateLimitPolicyProvider interface (from core/RateLimitPolicyProvider.java:11-20) resolves the policy to enforce:
RateLimitPolicy (from model/RateLimitPolicy.java:7-24) contains:
Default behavior
TheAnnotationRateLimitPolicyProvider (from support/AnnotationRateLimitPolicyProvider.java:14-30) reads policy directly from the annotation:
package com.example.ratelimit;
import io.github.v4runsharma.ratelimiter.annotation.RateLimit;
import io.github.v4runsharma.ratelimiter.core.RateLimitContext;
import io.github.v4runsharma.ratelimiter.core.RateLimitPolicyProvider;
import io.github.v4runsharma.ratelimiter.model.RateLimitPolicy;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class ConfigurablePolicyProvider implements RateLimitPolicyProvider {
private final Map<String, PolicyOverride> overrides = new ConcurrentHashMap<>();
@Override
public RateLimitPolicy resolvePolicy(RateLimitContext context) {
RateLimit annotation = context.getAnnotation();
// Check for override by name
if (annotation.name() != null && !annotation.name().isBlank()) {
PolicyOverride override = overrides.get(annotation.name());
if (override != null) {
return new RateLimitPolicy(
override.limit(),
Duration.ofSeconds(override.windowSeconds()),
override.scope()
);
}
}
// Fall back to annotation values
Duration window = Duration.of(
annotation.duration(),
annotation.timeUnit().toChronoUnit()
);
String scope = annotation.scope();
if (scope == null || scope.isBlank()) {
scope = "global";
}
return new RateLimitPolicy(annotation.limit(), window, scope);
}
public void setOverride(String name, int limit, long windowSeconds, String scope) {
overrides.put(name, new PolicyOverride(limit, windowSeconds, scope));
}
private record PolicyOverride(int limit, long windowSeconds, String scope) {}
}
import io.github.v4runsharma.ratelimiter.core.RateLimitPolicyProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class RateLimiterConfig {
@Bean
@Primary
public RateLimitPolicyProvider policyProvider() {
return new ConfigurablePolicyProvider();
}
}
import io.github.v4runsharma.ratelimiter.annotation.RateLimit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ApiController {
@RateLimit(
name = "api-search",
limit = 10, // Default, can be overridden
duration = 1
)
@GetMapping("/api/search")
public List<Result> search(@RequestParam String query) {
return searchService.search(query);
}
}
import com.example.ratelimit.ConfigurablePolicyProvider;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/ratelimit")
public class RateLimitAdminController {
private final ConfigurablePolicyProvider policyProvider;
public RateLimitAdminController(ConfigurablePolicyProvider policyProvider) {
this.policyProvider = policyProvider;
}
@PostMapping("/override")
public void setOverride(
@RequestParam String name,
@RequestParam int limit,
@RequestParam long windowSeconds,
@RequestParam(defaultValue = "global") String scope
) {
policyProvider.setOverride(name, limit, windowSeconds, scope);
}
}
Example: Properties-based provider
Load policies fromapplication.properties or application.yml:
application.yml: