Skip to content

Extract rate limiter configuration properties into separate classes - #4242

Open
AryamannSingh7 wants to merge 1 commit into
spring-cloud:mainfrom
AryamannSingh7:feature/4233-extract-ratelimiter-config-properties
Open

Extract rate limiter configuration properties into separate classes#4242
AryamannSingh7 wants to merge 1 commit into
spring-cloud:mainfrom
AryamannSingh7:feature/4233-extract-ratelimiter-config-properties

Conversation

@AryamannSingh7

Copy link
Copy Markdown

Closes gh-4233.

Background

@spencergibb — following your direction in this comment ("I like number three, but also extract separate configuration properties classes").

The commons half is already done. 679cf202 ("Skip resetting beans to default values if there is no default constructor") added a hasDefaultConstructor guard to ConfigurationPropertiesRebinder.resetBeanToDefaults, closing spring-cloud/spring-cloud-commons#1700. I checked it A/B: against spring-cloud-context 5.0.2 the WARN from the issue fires, against 5.0.3-SNAPSHOT it is silent, logging No default constructor for ...; skipping property reset before rebinding at debug instead. Since gateway builds against 5.0.3-SNAPSHOT, that warning should already be gone here.

So this PR is only the extraction.

Why it still matters

The commons guard only changes the log level — the reset is skipped either way. So a property removed from the environment does not revert to its class default on refresh; it keeps its previously bound value. RedisRateLimiter and RequestRateLimiterGatewayFilterFactory are the only two @ConfigurationProperties beans in the webflux server without a no-arg constructor (SetStatusGatewayFilterFactory, XForwardedHeadersFilter and RemoveHopByHopHeadersFilter all have one), so they are the only two affected.

What changed

  • New RedisRateLimiterProperties and RequestRateLimiterProperties, both with default constructors, holding the state that was previously bound onto the beans themselves.
  • Prefixes are unchanged, so no property names change for users.
  • The existing accessors are kept as deprecated delegates.

Preserving redis-rate-limiter.config.*

Worth calling out separately, since it is not obvious from the diff.

RedisRateLimiter inherits public Map<String, Config> getConfig() from AbstractStatefulConfigurable. That map was bindable only because the rate limiter was itself the @ConfigurationProperties bean — so moving the header/flag fields out and dropping the annotation would have silently broken spring.cloud.gateway.server.webflux.redis-rate-limiter.config.<routeId>.*.

The map therefore now lives on RedisRateLimiterProperties, and RedisRateLimiter#getConfig() reads through to it. Binding is unchanged from a user's point of view, and it is now rebound on refresh as well. RateLimiterPropertiesBindingTests#perRouteConfigIsStillBound covers this — it fails if the map is left on the rate limiter.

Open questions

Both carried over from my earlier comment on the issue. Happy to change either — neither is baked in deeply:

  1. The old accessors (setIncludeHeaders and friends, isDenyEmptyKey, isThrowOnLimit) are public API. I kept them as @Deprecated(since = "5.0.3") delegates. Would you rather they were removed outright in 5.x?
  2. I left the other three @ConfigurationProperties beans alone, since they have no-arg constructors and are not affected. Extend the same treatment to them for consistency, or leave them?

Testing

Added RateLimiterPropertiesBindingTests (binding, injection, and the per-route config case above) plus unit tests for each new properties class. Existing RedisRateLimiterTests, RedisRateLimiterLuaScriptTests, RedisRateLimiterConfigTests, RequestRateLimiterGatewayFilterFactoryTests and GatewayAutoConfigurationTests pass unchanged.

One note: docs/modules/ROOT/partials/_configprops.adoc is generated, so I left it untouched. Regenerating it should drop the request-rate-limiter.default-key-resolver and .default-rate-limiter entries, which came from getter-only metadata on the filter factory rather than from bindable properties.

RedisRateLimiter and RequestRateLimiterGatewayFilterFactory were
themselves @ConfigurationProperties beans, but neither has a no-arg
constructor because their collaborators are constructor injected. On a
refresh, ConfigurationPropertiesRebinder cannot build a throwaway
defaults instance for such a bean, so it skips resetting the properties
to their class defaults before rebinding. A property removed from the
environment therefore keeps its previously bound value rather than
reverting.

Move the bound state onto dedicated RedisRateLimiterProperties and
RequestRateLimiterProperties classes, which have default constructors
and so can be reset and rebound normally. The property prefixes are
unchanged, so no configuration keys change for users, and the existing
accessors are retained as deprecated delegates.

RedisRateLimiter also inherits a bindable per-route config map from
AbstractStatefulConfigurable, which was populated only because the rate
limiter was itself the @ConfigurationProperties bean. That map now lives
on RedisRateLimiterProperties and RedisRateLimiter#getConfig() reads
through to it, so redis-rate-limiter.config.* binds exactly as before
and is now rebound on refresh as well.

Closes spring-cloudgh-4233

Signed-off-by: Aryamann Singh <107678802+AryamannSingh7@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RedisRateLimiter and RequestRateLimiterGatewayFilterFactory lack default constructors, causing ConfigurationPropertiesRebinder WARN on config refresh

2 participants