Adds support for loading FilterSuppliers via beans

See gh-3250
This commit is contained in:
spencergibb
2025-04-14 18:20:15 -04:00
parent 51548b07c8
commit 8dbb5e6e69
7 changed files with 156 additions and 14 deletions

View File

@@ -37,6 +37,7 @@ import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcPropertiesBeanDefinitionRegistrar;
import org.springframework.cloud.gateway.server.mvc.config.RouterFunctionHolderFactory;
import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration;
import org.springframework.cloud.gateway.server.mvc.filter.FilterBeanFactoryDiscoverer;
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter;
import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.RequestHttpHeadersFilter;
@@ -86,8 +87,9 @@ public class GatewayServerMvcAutoConfiguration {
}
@Bean
public RouterFunctionHolderFactory routerFunctionHolderFactory(Environment env, BeanFactory beanFactory) {
return new RouterFunctionHolderFactory(env, beanFactory);
public RouterFunctionHolderFactory routerFunctionHolderFactory(Environment env, BeanFactory beanFactory,
FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer) {
return new RouterFunctionHolderFactory(env, beanFactory, filterBeanFactoryDiscoverer);
}
@Bean

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.server.mvc.common;
import java.util.List;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectProvider;
public abstract class BeanFactoryGatewayDiscoverer extends AbstractGatewayDiscoverer {
protected final BeanFactory beanFactory;
protected BeanFactoryGatewayDiscoverer(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
protected <T> List<T> loadSuppliers(Class<T> supplierClass) {
ObjectProvider<T> beanProvider = beanFactory.getBeanProvider(supplierClass);
return beanProvider.orderedStream().toList();
}
}

View File

@@ -43,6 +43,7 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.cloud.gateway.server.mvc.common.Configurable;
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
import org.springframework.cloud.gateway.server.mvc.filter.FilterBeanFactoryDiscoverer;
import org.springframework.cloud.gateway.server.mvc.filter.FilterDiscoverer;
import org.springframework.cloud.gateway.server.mvc.handler.HandlerDiscoverer;
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionDefinition;
@@ -58,6 +59,7 @@ import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscovere
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.log.LogMessage;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.function.HandlerFilterFunction;
@@ -109,14 +111,18 @@ public class RouterFunctionHolderFactory {
private final BeanFactory beanFactory;
private final FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer;
@Deprecated
public RouterFunctionHolderFactory(Environment env) {
this(env, null);
this(env, null, null);
}
public RouterFunctionHolderFactory(Environment env, BeanFactory beanFactory) {
public RouterFunctionHolderFactory(Environment env, BeanFactory beanFactory,
FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer) {
this.env = env;
this.beanFactory = beanFactory;
this.filterBeanFactoryDiscoverer = filterBeanFactoryDiscoverer;
}
/**
@@ -247,7 +253,9 @@ public class RouterFunctionHolderFactory {
lowerPrecedenceFilters.forEach(builder::filter);
// translate filters
MultiValueMap<String, OperationMethod> filterOperations = filterDiscoverer.getOperations();
MultiValueMap<String, OperationMethod> filterOperations = new LinkedMultiValueMap<>();
filterOperations.addAll(filterBeanFactoryDiscoverer.getOperations());
filterOperations.addAll(filterDiscoverer.getOperations());
routeProperties.getFilters().forEach(filterProperties -> {
Map<String, Object> args = new LinkedHashMap<>(filterProperties.getArgs());
translate(filterOperations, filterProperties.getName(), args, HandlerFilterFunction.class, builder::filter);

View File

@@ -19,17 +19,58 @@ package org.springframework.cloud.gateway.server.mvc.filter;
import java.util.Collections;
import java.util.function.Function;
import io.github.bucket4j.BucketConfiguration;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.gateway.server.mvc.config.RouteProperties;
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionDefinition;
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
@AutoConfiguration
public class FilterAutoConfiguration {
@Bean
public FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer(BeanFactory beanFactory) {
return new FilterBeanFactoryDiscoverer(beanFactory);
}
@Bean
public FilterFunctions.FilterSupplier filterFunctionsSupplier() {
return new FilterFunctions.FilterSupplier();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(BucketConfiguration.class)
static class Bucket4jFilterConfiguration {
@Bean
public Bucket4jFilterFunctions.FilterSupplier bucket4jFilterFunctionsSupplier() {
return new Bucket4jFilterFunctions.FilterSupplier();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CircuitBreaker.class)
static class CircuitBreakerFilterConfiguration {
@Bean
public CircuitBreakerFilterFunctions.FilterSupplier circuitBreakerFilterFunctionsSupplier() {
return new CircuitBreakerFilterFunctions.FilterSupplier();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(LoadBalancerClient.class)
static class LoadBalancerHandlerConfiguration {
@Bean
@@ -41,4 +82,26 @@ public class FilterAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
static class RetryFilterConfiguration {
@Bean
public RetryFilterFunctions.FilterSupplier retryFilterFunctionsSupplier() {
return new RetryFilterFunctions.FilterSupplier();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OAuth2AuthorizedClient.class)
static class TokenRelayFilterConfiguration {
@Bean
public TokenRelayFilterFunctions.FilterSupplier tokenRelayFilterFunctionsSupplier() {
return new TokenRelayFilterFunctions.FilterSupplier();
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.gateway.server.mvc.filter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.gateway.server.mvc.common.BeanFactoryGatewayDiscoverer;
import org.springframework.web.servlet.function.HandlerFilterFunction;
public class FilterBeanFactoryDiscoverer extends BeanFactoryGatewayDiscoverer {
protected FilterBeanFactoryDiscoverer(BeanFactory beanFactory) {
super(beanFactory);
}
@Override
public void discover() {
doDiscover(FilterSupplier.class, HandlerFilterFunction.class);
}
}

View File

@@ -15,13 +15,6 @@
#
#
org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier=\
org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions.FilterSupplier,\
org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.FilterSupplier,\
org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions.FilterSupplier,\
org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions.FilterSupplier,\
org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.FilterSupplier
org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier=\
org.springframework.cloud.gateway.server.mvc.predicate.MvcPredicateSupplier,\
org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.PredicateSupplier

View File

@@ -35,6 +35,7 @@ import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration;
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter;
import org.springframework.cloud.gateway.server.mvc.filter.RemoveContentLengthRequestHeadersFilter;
@@ -44,6 +45,7 @@ import org.springframework.cloud.gateway.server.mvc.filter.RemoveHttp2StatusResp
import org.springframework.cloud.gateway.server.mvc.filter.TransferEncodingNormalizationRequestHeadersFilter;
import org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilter;
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter;
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@@ -109,7 +111,8 @@ public class GatewayServerMvcAutoConfigurationTests {
@Test
void filterEnabledPropertiesWork() {
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(GatewayServerMvcAutoConfiguration.class,
.withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class,
HandlerFunctionAutoConfiguration.class, GatewayServerMvcAutoConfiguration.class,
HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class,
RestClientAutoConfiguration.class, SslAutoConfiguration.class))
.withPropertyValues("spring.cloud.gateway.mvc.form-filter.enabled=false",
@@ -161,7 +164,8 @@ public class GatewayServerMvcAutoConfigurationTests {
@Test
void bootHttpClientPropertiesWork() {
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(GatewayServerMvcAutoConfiguration.class,
.withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class,
HandlerFunctionAutoConfiguration.class, GatewayServerMvcAutoConfiguration.class,
HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class,
RestClientAutoConfiguration.class, SslAutoConfiguration.class))
.withPropertyValues("spring.http.client.connect-timeout=1s", "spring.http.client.read-timeout=2s",