From 51548b07c842d51104886fc3966769fcdd7ca15a Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 14 Apr 2025 17:40:19 -0400 Subject: [PATCH 1/6] Allow bean registration of server-webmcv components This replaces the spring.factories mechanism. Fixes gh-3250 --- .../GatewayServerMvcAutoConfiguration.java | 9 +- .../GatewayMvcAotRuntimeHintsRegistrar.java | 4 +- .../config/RouterFunctionHolderFactory.java | 83 +++++++++---- ...lier.java => FilterAutoConfiguration.java} | 35 +++--- .../mvc/handler/DefaultHandlerSupplier.java | 113 ------------------ .../HandlerFunctionAutoConfiguration.java | 93 ++++++++++++++ .../handler/HandlerFunctionDefinition.java | 45 +++++++ .../main/resources/META-INF/spring.factories | 4 - ...ot.autoconfigure.AutoConfiguration.imports | 2 + 9 files changed, 222 insertions(+), 166 deletions(-) rename spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/{LoadBalancerHandlerSupplier.java => FilterAutoConfiguration.java} (52%) delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/DefaultHandlerSupplier.java create mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionAutoConfiguration.java create mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionDefinition.java diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java index dabf7d2dfa..00cecf8c48 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java @@ -18,6 +18,7 @@ import java.util.Map; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -35,6 +36,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.FormFilter; import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter; import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.RequestHttpHeadersFilter; @@ -47,6 +49,7 @@ 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.filter.XForwardedRequestHeadersFilterProperties; +import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionAutoConfiguration; import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchange; import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchangeHandlerFunction; import org.springframework.cloud.gateway.server.mvc.handler.RestClientProxyExchange; @@ -70,7 +73,7 @@ * @author Jürgen Wißkirchen */ @AutoConfiguration(after = { HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class, - RestClientAutoConfiguration.class }) + RestClientAutoConfiguration.class, FilterAutoConfiguration.class, HandlerFunctionAutoConfiguration.class }) @ConditionalOnProperty(name = "spring.cloud.gateway.mvc.enabled", matchIfMissing = true) @Import(GatewayMvcPropertiesBeanDefinitionRegistrar.class) @ImportRuntimeHints(GatewayMvcAotRuntimeHintsRegistrar.class) @@ -83,8 +86,8 @@ public static ArgumentSupplierBeanPostProcessor argumentSupplierBeanPostProcesso } @Bean - public RouterFunctionHolderFactory routerFunctionHolderFactory(Environment env) { - return new RouterFunctionHolderFactory(env); + public RouterFunctionHolderFactory routerFunctionHolderFactory(Environment env, BeanFactory beanFactory) { + return new RouterFunctionHolderFactory(env, beanFactory); } @Bean diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java index e4482d3a2e..08a879c1f9 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java @@ -31,7 +31,6 @@ import org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions; import org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions; import org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions; -import org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerHandlerSupplier; import org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions; import org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions; import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions; @@ -46,8 +45,9 @@ */ public class GatewayMvcAotRuntimeHintsRegistrar implements RuntimeHintsRegistrar { + // TODO: fix AOT HINTS private static final Set> FUNCTION_PROVIDERS = Set.of(HandlerFunctions.class, - LoadBalancerHandlerSupplier.class, FilterFunctions.class, BeforeFilterFunctions.class, + /* LoadBalancerHandlerSupplier.class, */ FilterFunctions.class, BeforeFilterFunctions.class, AfterFilterFunctions.class, TokenRelayFilterFunctions.class, BodyFilterFunctions.class, CircuitBreakerFilterFunctions.class, GatewayRouterFunctions.class, LoadBalancerFilterFunctions.class, GatewayRequestPredicates.class, Bucket4jFilterFunctions.class); diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java index 6bc52a5285..babf4ef111 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java @@ -28,10 +28,14 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Function; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanNotOfRequiredTypeException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.handler.IgnoreTopLevelConverterNotFoundBindHandler; @@ -41,6 +45,7 @@ import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; 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; import org.springframework.cloud.gateway.server.mvc.invoke.InvocationContext; import org.springframework.cloud.gateway.server.mvc.invoke.OperationArgumentResolver; import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameter; @@ -102,8 +107,16 @@ public String toString() { private final ParameterValueMapper parameterValueMapper = new ConversionServiceParameterValueMapper(); + private final BeanFactory beanFactory; + + @Deprecated public RouterFunctionHolderFactory(Environment env) { + this(env, null); + } + + public RouterFunctionHolderFactory(Environment env, BeanFactory beanFactory) { this.env = env; + this.beanFactory = beanFactory; } /** @@ -153,37 +166,57 @@ private RouterFunction getRouterFunction(RouteProperties routeProperties, String // TODO: cache? // translate handlerFunction String scheme = routeProperties.getUri().getScheme(); - Map handlerArgs = new HashMap<>(); - Optional handlerOperationMethod = findOperation(handlerOperations, - scheme.toLowerCase(Locale.ROOT), handlerArgs); - if (handlerOperationMethod.isEmpty()) { - // single RouteProperties param - handlerArgs.clear(); - String routePropsKey = StringUtils.uncapitalize(RouteProperties.class.getSimpleName()); - handlerArgs.put(routePropsKey, routeProperties); - handlerOperationMethod = findOperation(handlerOperations, scheme.toLowerCase(Locale.ROOT), handlerArgs); - if (handlerOperationMethod.isEmpty()) { - throw new IllegalStateException("Unable to find HandlerFunction for scheme: " + scheme); - } - } - NormalizedOperationMethod normalizedOpMethod = handlerOperationMethod.get(); - Object response = invokeOperation(normalizedOpMethod, normalizedOpMethod.getNormalizedArgs()); - HandlerFunction handlerFunction = null; // filters added by HandlerDiscoverer need to go last, so save them + HandlerFunction handlerFunction = null; List> lowerPrecedenceFilters = new ArrayList<>(); List> higherPrecedenceFilters = new ArrayList<>(); - if (response instanceof HandlerFunction) { - handlerFunction = (HandlerFunction) response; - } - else if (response instanceof HandlerDiscoverer.Result result) { - handlerFunction = result.getHandlerFunction(); - lowerPrecedenceFilters.addAll(result.getLowerPrecedenceFilters()); - higherPrecedenceFilters.addAll(result.getHigherPrecedenceFilters()); + + if (beanFactory != null) { + try { + // TODO: configurable bean name? + String name = scheme + "HandlerFunctionDefinition"; + Function factory = beanFactory.getBean(name, Function.class); + HandlerFunctionDefinition definition = (HandlerFunctionDefinition) factory.apply(routeProperties); + handlerFunction = definition.handlerFunction(); + lowerPrecedenceFilters.addAll(definition.lowerPrecedenceFilters()); + higherPrecedenceFilters.addAll(definition.higherPrecedenceFilters()); + } + catch (NoSuchBeanDefinitionException | BeanNotOfRequiredTypeException | ClassCastException e) { + log.trace(LogMessage.format("Unable to locate bean of HandlerFunction for scheme %s", scheme), e); + } } + if (handlerFunction == null) { - throw new IllegalStateException( - "Unable to find HandlerFunction for scheme: " + scheme + " and response " + response); + Map handlerArgs = new HashMap<>(); + Optional handlerOperationMethod = findOperation(handlerOperations, + scheme.toLowerCase(Locale.ROOT), handlerArgs); + if (handlerOperationMethod.isEmpty()) { + // single RouteProperties param + handlerArgs.clear(); + String routePropsKey = StringUtils.uncapitalize(RouteProperties.class.getSimpleName()); + handlerArgs.put(routePropsKey, routeProperties); + handlerOperationMethod = findOperation(handlerOperations, scheme.toLowerCase(Locale.ROOT), handlerArgs); + if (handlerOperationMethod.isEmpty()) { + throw new IllegalStateException("Unable to find HandlerFunction for scheme: " + scheme); + } + } + + NormalizedOperationMethod normalizedOpMethod = handlerOperationMethod.get(); + Object response = invokeOperation(normalizedOpMethod, normalizedOpMethod.getNormalizedArgs()); + + if (response instanceof HandlerFunction) { + handlerFunction = (HandlerFunction) response; + } + else if (response instanceof HandlerDiscoverer.Result result) { + handlerFunction = result.getHandlerFunction(); + lowerPrecedenceFilters.addAll(result.getLowerPrecedenceFilters()); + higherPrecedenceFilters.addAll(result.getHigherPrecedenceFilters()); + } + if (handlerFunction == null) { + throw new IllegalStateException( + "Unable to find HandlerFunction for scheme: " + scheme + " and response " + response); + } } // translate predicates diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerHandlerSupplier.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java similarity index 52% rename from spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerHandlerSupplier.java rename to spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java index c2a0ad58b0..c2cd62a1c9 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerHandlerSupplier.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * 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. @@ -16,32 +16,29 @@ package org.springframework.cloud.gateway.server.mvc.filter; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; +import java.util.function.Function; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.cloud.gateway.server.mvc.config.RouteProperties; -import org.springframework.cloud.gateway.server.mvc.handler.HandlerDiscoverer; +import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionDefinition; import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions; -import org.springframework.cloud.gateway.server.mvc.handler.HandlerSupplier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; -public class LoadBalancerHandlerSupplier implements HandlerSupplier { +@AutoConfiguration +public class FilterAutoConfiguration { - @Override - public Collection get() { - return Arrays.asList(getClass().getMethods()); - } + @Configuration(proxyBeanMethods = false) + static class LoadBalancerHandlerConfiguration { - public static HandlerDiscoverer.Result lb(RouteProperties routeProperties) { - return lb(routeProperties.getUri()); - } + @Bean + public Function lbHandlerFunctionDefinition() { + return routeProperties -> new HandlerFunctionDefinition.Default("lb", HandlerFunctions.http(), + Collections.emptyList(), + Collections.singletonList(LoadBalancerFilterFunctions.lb(routeProperties.getUri().getHost()))); + } - public static HandlerDiscoverer.Result lb(URI uri) { - // TODO: how to do something other than http - return new HandlerDiscoverer.Result(HandlerFunctions.http(), Collections.emptyList(), - Collections.singletonList(LoadBalancerFilterFunctions.lb(uri.getHost()))); } } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/DefaultHandlerSupplier.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/DefaultHandlerSupplier.java deleted file mode 100644 index 0adc142eca..0000000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/DefaultHandlerSupplier.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.handler; - -import java.lang.reflect.Method; -import java.net.URI; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; - -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.cloud.gateway.server.mvc.config.RouteProperties; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.ServerResponse; - -class DefaultHandlerSupplier implements HandlerSupplier { - - @Override - public Collection get() { - return Arrays.asList(getClass().getMethods()); - } - - public static HandlerDiscoverer.Result fn(RouteProperties routeProperties) { - // fn:fnName - return fn(routeProperties.getUri().getSchemeSpecificPart()); - } - - public static HandlerDiscoverer.Result fn(String functionName) { - return new HandlerDiscoverer.Result(HandlerFunctions.fn(functionName), Collections.emptyList(), - Collections.emptyList()); - } - - public static HandlerDiscoverer.Result forward(RouteProperties routeProperties) { - return forward(routeProperties.getId(), routeProperties.getUri()); - } - - public static HandlerDiscoverer.Result forward(String id, URI uri) { - return new HandlerDiscoverer.Result(HandlerFunctions.forward(uri.getPath()), Collections.emptyList()); - } - - public static HandlerDiscoverer.Result http(RouteProperties routeProperties) { - return http(routeProperties.getId(), routeProperties.getUri()); - } - - public static HandlerDiscoverer.Result http(String id, URI uri) { - HandlerFunction http = HandlerFunctions.http(); - return getResult(id, uri, http); - } - - public static HandlerDiscoverer.Result https(RouteProperties routeProperties) { - return https(routeProperties.getId(), routeProperties.getUri()); - } - - public static HandlerDiscoverer.Result https(String id, URI uri) { - return getResult(id, uri, HandlerFunctions.https()); - } - - public static HandlerDiscoverer.Result no(RouteProperties routeProperties) { - return no(routeProperties.getId(), routeProperties.getUri()); - } - - public static HandlerDiscoverer.Result no(String id, URI uri) { - return getResult(id, uri, HandlerFunctions.no()); - } - - // for properties - public static HandlerDiscoverer.Result stream(RouteProperties routeProperties) { - // stream:bindingName - return stream(routeProperties.getUri().getSchemeSpecificPart()); - } - - public static HandlerDiscoverer.Result stream(String bindingName) { - return new HandlerDiscoverer.Result(HandlerFunctions.stream(bindingName), Collections.emptyList(), - Collections.emptyList()); - } - - private static HandlerDiscoverer.Result getResult(String id, URI uri, - HandlerFunction handlerFunction) { - HandlerFilterFunction setId = setIdFilter(id); - HandlerFilterFunction setRequest = setRequestUrlFilter(uri); - return new HandlerDiscoverer.Result(handlerFunction, Arrays.asList(setId, setRequest), Collections.emptyList()); - } - - private static HandlerFilterFunction setIdFilter(String id) { - return (request, next) -> { - MvcUtils.setRouteId(request, id); - return next.handle(request); - }; - } - - private static HandlerFilterFunction setRequestUrlFilter(URI uri) { - return (request, next) -> { - MvcUtils.setRequestUrl(request, uri); - return next.handle(request); - }; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionAutoConfiguration.java new file mode 100644 index 0000000000..3e4d3781de --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionAutoConfiguration.java @@ -0,0 +1,93 @@ +/* + * 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.handler; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.function.Function; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; +import org.springframework.cloud.gateway.server.mvc.config.RouteProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.web.servlet.function.HandlerFilterFunction; +import org.springframework.web.servlet.function.HandlerFunction; +import org.springframework.web.servlet.function.ServerResponse; + +@AutoConfiguration +public class HandlerFunctionAutoConfiguration { + + @Bean + public Function fnHandlerFunctionDefinition() { + return routeProperties -> new HandlerFunctionDefinition.Default("fn", + HandlerFunctions.fn(routeProperties.getUri().getSchemeSpecificPart())); + } + + @Bean + public Function forwardHandlerFunctionDefinition() { + return routeProperties -> new HandlerFunctionDefinition.Default("forward", + HandlerFunctions.forward(routeProperties.getUri().getPath())); + } + + @Bean + public Function httpHandlerFunctionDefinition() { + return routeProperties -> getResult("http", routeProperties.getId(), routeProperties.getUri(), + HandlerFunctions.http()); + } + + @Bean + public Function httpsHandlerFunctionDefinition() { + return routeProperties -> getResult("https", routeProperties.getId(), routeProperties.getUri(), + HandlerFunctions.https()); + } + + @Bean + public Function noHandlerFunctionDefinition() { + return routeProperties -> getResult("no", routeProperties.getId(), routeProperties.getUri(), + HandlerFunctions.no()); + } + + @Bean + public Function streamHandlerFunctionDefinition() { + return routeProperties -> new HandlerFunctionDefinition.Default("stream", + HandlerFunctions.stream(routeProperties.getUri().getSchemeSpecificPart())); + } + + private static HandlerFunctionDefinition getResult(String scheme, String id, URI uri, + HandlerFunction handlerFunction) { + HandlerFilterFunction setId = setIdFilter(id); + HandlerFilterFunction setRequest = setRequestUrlFilter(uri); + return new HandlerFunctionDefinition.Default(scheme, handlerFunction, Arrays.asList(setId, setRequest), + Collections.emptyList()); + } + + private static HandlerFilterFunction setIdFilter(String id) { + return (request, next) -> { + MvcUtils.setRouteId(request, id); + return next.handle(request); + }; + } + + private static HandlerFilterFunction setRequestUrlFilter(URI uri) { + return (request, next) -> { + MvcUtils.setRequestUrl(request, uri); + return next.handle(request); + }; + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionDefinition.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionDefinition.java new file mode 100644 index 0000000000..12d79fe6ba --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionDefinition.java @@ -0,0 +1,45 @@ +/* + * 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.handler; + +import java.util.Collections; +import java.util.List; + +import org.springframework.web.servlet.function.HandlerFilterFunction; +import org.springframework.web.servlet.function.HandlerFunction; +import org.springframework.web.servlet.function.ServerResponse; + +public interface HandlerFunctionDefinition { + + HandlerFunction handlerFunction(); + + List> lowerPrecedenceFilters(); + + List> higherPrecedenceFilters(); + + record Default(String scheme, HandlerFunction handlerFunction, + List> lowerPrecedenceFilters, + List> higherPrecedenceFilters) + implements + HandlerFunctionDefinition { + + public Default(String scheme, HandlerFunction handlerFunction) { + this(scheme, handlerFunction, Collections.emptyList(), Collections.emptyList()); + } + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories index b7002f1553..452cfce5e1 100644 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories @@ -22,10 +22,6 @@ org.springframework.cloud.gateway.server.mvc.filter.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.handler.HandlerSupplier=\ - org.springframework.cloud.gateway.server.mvc.handler.DefaultHandlerSupplier,\ - org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerHandlerSupplier - 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 diff --git a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 9428294363..2c2f8596ed 100644 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,5 +1,7 @@ org.springframework.cloud.gateway.server.mvc.GatewayServerMvcAutoConfiguration org.springframework.cloud.gateway.server.mvc.GatewayMvcClassPathWarningAutoConfiguration +org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration +org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionAutoConfiguration org.springframework.cloud.gateway.server.mvc.handler.GatewayMultipartAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration org.springframework.cloud.gateway.server.mvc.config.DefaultFunctionConfiguration \ No newline at end of file From 8dbb5e6e69f7b7e5ecb7c61420ad694bb5864558 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 14 Apr 2025 18:20:15 -0400 Subject: [PATCH 2/6] Adds support for loading FilterSuppliers via beans See gh-3250 --- .../GatewayServerMvcAutoConfiguration.java | 6 +- .../common/BeanFactoryGatewayDiscoverer.java | 38 +++++++++++ .../config/RouterFunctionHolderFactory.java | 14 ++++- .../mvc/filter/FilterAutoConfiguration.java | 63 +++++++++++++++++++ .../filter/FilterBeanFactoryDiscoverer.java | 34 ++++++++++ .../main/resources/META-INF/spring.factories | 7 --- ...atewayServerMvcAutoConfigurationTests.java | 8 ++- 7 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/BeanFactoryGatewayDiscoverer.java create mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterBeanFactoryDiscoverer.java diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java index 00cecf8c48..12bcbaafef 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java @@ -37,6 +37,7 @@ 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 static ArgumentSupplierBeanPostProcessor argumentSupplierBeanPostProcesso } @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 diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/BeanFactoryGatewayDiscoverer.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/BeanFactoryGatewayDiscoverer.java new file mode 100644 index 0000000000..5a51957344 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/BeanFactoryGatewayDiscoverer.java @@ -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 List loadSuppliers(Class supplierClass) { + ObjectProvider beanProvider = beanFactory.getBeanProvider(supplierClass); + return beanProvider.orderedStream().toList(); + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java index babf4ef111..86d9b6619c 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java @@ -43,6 +43,7 @@ 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.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 String toString() { 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 @@ else if (response instanceof HandlerDiscoverer.Result result) { lowerPrecedenceFilters.forEach(builder::filter); // translate filters - MultiValueMap filterOperations = filterDiscoverer.getOperations(); + MultiValueMap filterOperations = new LinkedMultiValueMap<>(); + filterOperations.addAll(filterBeanFactoryDiscoverer.getOperations()); + filterOperations.addAll(filterDiscoverer.getOperations()); routeProperties.getFilters().forEach(filterProperties -> { Map args = new LinkedHashMap<>(filterProperties.getArgs()); translate(filterOperations, filterProperties.getName(), args, HandlerFilterFunction.class, builder::filter); diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java index c2cd62a1c9..ad260ccbca 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java @@ -19,17 +19,58 @@ 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 Function lbHandlerFunctionDef } + @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(); + } + + } + } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterBeanFactoryDiscoverer.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterBeanFactoryDiscoverer.java new file mode 100644 index 0000000000..cb9f47f0bc --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterBeanFactoryDiscoverer.java @@ -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); + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories index 452cfce5e1..22ae1530cb 100644 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories @@ -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 diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java index 7eb5640525..070b719f77 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java @@ -35,6 +35,7 @@ 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.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 @@ void gatewayHttpClientPropertiesWork() { @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", From f4de53ebcea0f1237c6bf0e97c4f5fb66b40cad2 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 15 Apr 2025 08:40:43 -0400 Subject: [PATCH 3/6] Adds support for loading PredicateSuppliers via beans See gh-3250 --- .../GatewayServerMvcAutoConfiguration.java | 11 +++-- .../config/RouterFunctionHolderFactory.java | 19 +++++-- .../predicate/PredicateAutoConfiguration.java | 41 ++++++++++++++++ .../PredicateBeanFactoryDiscoverer.java | 34 +++++++++++++ .../main/resources/META-INF/spring.factories | 4 -- ...ot.autoconfigure.AutoConfiguration.imports | 1 + ...atewayServerMvcAutoConfigurationTests.java | 5 +- .../mvc/filter/FilterDiscovererTests.java | 45 +++++++++++++++++ .../PredicateBeanFactoryDiscovererTests.java | 49 +++++++++++++++++++ .../mvc/test/TestPredicateSupplier.java | 37 ++++++++++++++ .../test/resources/META-INF/spring.factories | 3 ++ 11 files changed, 236 insertions(+), 13 deletions(-) create mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateAutoConfiguration.java create mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscoverer.java create mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscovererTests.java create mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscovererTests.java create mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestPredicateSupplier.java diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java index 12bcbaafef..dfdbd1bcde 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java @@ -54,6 +54,8 @@ import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchange; import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchangeHandlerFunction; import org.springframework.cloud.gateway.server.mvc.handler.RestClientProxyExchange; +import org.springframework.cloud.gateway.server.mvc.predicate.PredicateAutoConfiguration; +import org.springframework.cloud.gateway.server.mvc.predicate.PredicateBeanFactoryDiscoverer; import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscoverer; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; @@ -74,7 +76,8 @@ * @author Jürgen Wißkirchen */ @AutoConfiguration(after = { HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class, - RestClientAutoConfiguration.class, FilterAutoConfiguration.class, HandlerFunctionAutoConfiguration.class }) + RestClientAutoConfiguration.class, FilterAutoConfiguration.class, HandlerFunctionAutoConfiguration.class, + PredicateAutoConfiguration.class }) @ConditionalOnProperty(name = "spring.cloud.gateway.mvc.enabled", matchIfMissing = true) @Import(GatewayMvcPropertiesBeanDefinitionRegistrar.class) @ImportRuntimeHints(GatewayMvcAotRuntimeHintsRegistrar.class) @@ -88,8 +91,10 @@ public static ArgumentSupplierBeanPostProcessor argumentSupplierBeanPostProcesso @Bean public RouterFunctionHolderFactory routerFunctionHolderFactory(Environment env, BeanFactory beanFactory, - FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer) { - return new RouterFunctionHolderFactory(env, beanFactory, filterBeanFactoryDiscoverer); + FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer, + PredicateBeanFactoryDiscoverer predicateBeanFactoryDiscoverer) { + return new RouterFunctionHolderFactory(env, beanFactory, filterBeanFactoryDiscoverer, + predicateBeanFactoryDiscoverer); } @Bean diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java index 86d9b6619c..63113f668b 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java @@ -55,6 +55,7 @@ import org.springframework.cloud.gateway.server.mvc.invoke.convert.ConversionServiceParameterValueMapper; import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod; import org.springframework.cloud.gateway.server.mvc.invoke.reflect.ReflectiveOperationInvoker; +import org.springframework.cloud.gateway.server.mvc.predicate.PredicateBeanFactoryDiscoverer; import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscoverer; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.Environment; @@ -113,16 +114,20 @@ public String toString() { private final FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer; + private final PredicateBeanFactoryDiscoverer predicateBeanFactoryDiscoverer; + @Deprecated public RouterFunctionHolderFactory(Environment env) { - this(env, null, null); + this(env, null, null, null); } public RouterFunctionHolderFactory(Environment env, BeanFactory beanFactory, - FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer) { + FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer, + PredicateBeanFactoryDiscoverer predicateBeanFactoryDiscoverer) { this.env = env; this.beanFactory = beanFactory; this.filterBeanFactoryDiscoverer = filterBeanFactoryDiscoverer; + this.predicateBeanFactoryDiscoverer = predicateBeanFactoryDiscoverer; } /** @@ -226,7 +231,11 @@ else if (response instanceof HandlerDiscoverer.Result result) { } // translate predicates - MultiValueMap predicateOperations = predicateDiscoverer.getOperations(); + MultiValueMap predicateOperations = new LinkedMultiValueMap<>(); + if (predicateBeanFactoryDiscoverer != null) { + predicateOperations.addAll(predicateBeanFactoryDiscoverer.getOperations()); + } + predicateOperations.addAll(predicateDiscoverer.getOperations()); final AtomicReference predicate = new AtomicReference<>(); routeProperties.getPredicates().forEach(predicateProperties -> { @@ -254,7 +263,9 @@ else if (response instanceof HandlerDiscoverer.Result result) { // translate filters MultiValueMap filterOperations = new LinkedMultiValueMap<>(); - filterOperations.addAll(filterBeanFactoryDiscoverer.getOperations()); + if (filterBeanFactoryDiscoverer != null) { + filterOperations.addAll(filterBeanFactoryDiscoverer.getOperations()); + } filterOperations.addAll(filterDiscoverer.getOperations()); routeProperties.getFilters().forEach(filterProperties -> { Map args = new LinkedHashMap<>(filterProperties.getArgs()); diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateAutoConfiguration.java new file mode 100644 index 0000000000..4a3c612d80 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateAutoConfiguration.java @@ -0,0 +1,41 @@ +/* + * 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.predicate; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +public class PredicateAutoConfiguration { + + @Bean + public PredicateBeanFactoryDiscoverer predicateBeanFactoryDiscoverer(BeanFactory beanFactory) { + return new PredicateBeanFactoryDiscoverer(beanFactory); + } + + @Bean + MvcPredicateSupplier mvcPredicateSupplier() { + return new MvcPredicateSupplier(); + } + + @Bean + GatewayRequestPredicates.PredicateSupplier gatewayRequestPredicateSupplier() { + return new GatewayRequestPredicates.PredicateSupplier(); + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscoverer.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscoverer.java new file mode 100644 index 0000000000..90f77967cd --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscoverer.java @@ -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.predicate; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.gateway.server.mvc.common.BeanFactoryGatewayDiscoverer; +import org.springframework.web.servlet.function.RequestPredicate; + +public class PredicateBeanFactoryDiscoverer extends BeanFactoryGatewayDiscoverer { + + protected PredicateBeanFactoryDiscoverer(BeanFactory beanFactory) { + super(beanFactory); + } + + @Override + public void discover() { + doDiscover(PredicateSupplier.class, RequestPredicate.class); + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories index 22ae1530cb..c208d46687 100644 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories @@ -15,10 +15,6 @@ # # -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 - org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.cloud.gateway.server.mvc.GatewayServerMvcAutoConfiguration.GatewayHttpClientEnvironmentPostProcessor,\ org.springframework.cloud.gateway.server.mvc.common.MultipartEnvironmentPostProcessor diff --git a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 2c2f8596ed..8177285599 100644 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -3,5 +3,6 @@ org.springframework.cloud.gateway.server.mvc.GatewayMvcClassPathWarningAutoConfi org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionAutoConfiguration org.springframework.cloud.gateway.server.mvc.handler.GatewayMultipartAutoConfiguration +org.springframework.cloud.gateway.server.mvc.predicate.PredicateAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration org.springframework.cloud.gateway.server.mvc.config.DefaultFunctionConfiguration \ No newline at end of file diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java index 070b719f77..917c0a5425 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java @@ -46,6 +46,7 @@ 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.cloud.gateway.server.mvc.predicate.PredicateAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -111,7 +112,7 @@ public class GatewayServerMvcAutoConfigurationTests { @Test void filterEnabledPropertiesWork() { new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class, PredicateAutoConfiguration.class, HandlerFunctionAutoConfiguration.class, GatewayServerMvcAutoConfiguration.class, HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class, RestClientAutoConfiguration.class, SslAutoConfiguration.class)) @@ -164,7 +165,7 @@ void gatewayHttpClientPropertiesWork() { @Test void bootHttpClientPropertiesWork() { new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class, PredicateAutoConfiguration.class, HandlerFunctionAutoConfiguration.class, GatewayServerMvcAutoConfiguration.class, HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class, RestClientAutoConfiguration.class, SslAutoConfiguration.class)) diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscovererTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscovererTests.java new file mode 100644 index 0000000000..0edce13992 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscovererTests.java @@ -0,0 +1,45 @@ +/* + * 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.filter; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod; +import org.springframework.util.MultiValueMap; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +public class FilterDiscovererTests { + + @Test + void contextLoads() { + MultiValueMap operations = new FilterDiscoverer().getOperations(); + assertThat(operations).isNotEmpty(); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + static class Config { + + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscovererTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscovererTests.java new file mode 100644 index 0000000000..dcbe4ba1ff --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscovererTests.java @@ -0,0 +1,49 @@ +/* + * 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.predicate; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod; +import org.springframework.util.MultiValueMap; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +public class PredicateBeanFactoryDiscovererTests { + + @Autowired + PredicateBeanFactoryDiscoverer discoverer; + + @Test + void contextLoads() { + MultiValueMap operations = discoverer.getOperations(); + assertThat(operations).isNotEmpty(); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + static class Config { + + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestPredicateSupplier.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestPredicateSupplier.java new file mode 100644 index 0000000000..2ace83bd3f --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestPredicateSupplier.java @@ -0,0 +1,37 @@ +/* + * 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.test; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.List; + +import org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier; +import org.springframework.web.servlet.function.RequestPredicate; + +public class TestPredicateSupplier implements PredicateSupplier { + + public static RequestPredicate alwaysTrue() { + return request -> true; + } + + @Override + public Collection get() { + return List.of(TestPredicateSupplier.class.getMethods()); + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring.factories b/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring.factories index bca78c949a..b99d378a56 100644 --- a/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring.factories +++ b/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring.factories @@ -1,2 +1,5 @@ +org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier=\ + org.springframework.cloud.gateway.server.mvc.test.TestPredicateSupplier + org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier=\ org.springframework.cloud.gateway.server.mvc.test.TestFilterSupplier \ No newline at end of file From a3155ecc8b20a2a62f7fc2f17ebdf4ae47eedff4 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 15 Apr 2025 11:01:26 -0400 Subject: [PATCH 4/6] Use beanFactory conversionService See gh-3250 --- .../config/RouterFunctionHolderFactory.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java index 63113f668b..0973f657b6 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java @@ -36,6 +36,7 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanNotOfRequiredTypeException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.handler.IgnoreTopLevelConverterNotFoundBindHandler; @@ -57,6 +58,7 @@ import org.springframework.cloud.gateway.server.mvc.invoke.reflect.ReflectiveOperationInvoker; import org.springframework.cloud.gateway.server.mvc.predicate.PredicateBeanFactoryDiscoverer; import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscoverer; +import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.Environment; import org.springframework.core.log.LogMessage; @@ -116,6 +118,8 @@ public String toString() { private final PredicateBeanFactoryDiscoverer predicateBeanFactoryDiscoverer; + private final ConversionService conversionService; + @Deprecated public RouterFunctionHolderFactory(Environment env) { this(env, null, null, null); @@ -128,6 +132,17 @@ public RouterFunctionHolderFactory(Environment env, BeanFactory beanFactory, this.beanFactory = beanFactory; this.filterBeanFactoryDiscoverer = filterBeanFactoryDiscoverer; this.predicateBeanFactoryDiscoverer = predicateBeanFactoryDiscoverer; + if (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) { + if (configurableBeanFactory.getConversionService() != null) { + this.conversionService = configurableBeanFactory.getConversionService(); + } + else { + this.conversionService = DefaultConversionService.getSharedInstance(); + } + } + else { + this.conversionService = DefaultConversionService.getSharedInstance(); + } } /** @@ -347,7 +362,7 @@ private T invokeOperation(OperationMethod operationMethod, Map args, + private Object bindConfigurable(OperationMethod operationMethod, Map args, OperationParameter operationParameter) { Class configurableType = operationParameter.getType(); Configurable configurable = operationMethod.getMethod().getAnnotation(Configurable.class); @@ -357,8 +372,8 @@ private static Object bindConfigurable(OperationMethod operationMethod, Map bindable = Bindable.of(configurableType); List propertySources = Collections .singletonList(new MapConfigurationPropertySource(args)); - // TODO: potentially deal with conversion service - Binder binder = new Binder(propertySources, null, DefaultConversionService.getSharedInstance()); + + Binder binder = new Binder(propertySources, null, conversionService); Object config = binder.bindOrCreate("", bindable, new IgnoreTopLevelConverterNotFoundBindHandler()); return config; } From 0328eec480094895a7cc4028fb058fabb4d676fc Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 15 Apr 2025 15:16:15 -0400 Subject: [PATCH 5/6] Update docs See gh-3250 --- ...writing-custom-predicates-and-filters.adoc | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/writing-custom-predicates-and-filters.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/writing-custom-predicates-and-filters.adoc index 597b00fe38..1364150eff 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/writing-custom-predicates-and-filters.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/writing-custom-predicates-and-filters.adoc @@ -240,13 +240,12 @@ The above route will add a `X-Response-Id` header to the response. Note the use == How To Register Custom Predicates and Filters for Configuration -To use custom Predicates and Filters in external configuration you need to create a special Supplier class and register it in `META-INF/spring.factories`. +To use custom Predicates and Filters in external configuration you need to create a special Supplier class and register it a bean in the application context. === Registering Custom Predicates To register custom predicates you need to implement `PredicateSupplier`. The `PredicateDiscoverer` looks for static methods that return `RequestPredicates` to register. - SampleFilterSupplier.java [source,java] ---- @@ -254,7 +253,6 @@ package com.example; import org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier; -@Configuration class SamplePredicateSupplier implements PredicateSupplier { @Override @@ -265,7 +263,24 @@ class SamplePredicateSupplier implements PredicateSupplier { } ---- -You then need to add the class in `META-INF/spring.factories`. +To register the `PredicateSupplier` for use in config files, you then need to add the class as a bean as in the example below: + +.PredicateConfiguration.java +[source,java] +---- +package com.example; + +@Configuration +class PredicateConfiguration { + + @Bean + public SamplePredicateSupplier samplePredicateSupplier() { + return new SamplePredicateSupplier(); + } +} +---- + +The requirement to add the class to `META-INF/spring.factories` is deprecated and will be removed in the next major release. .META-INF/spring.factories [source] @@ -285,7 +300,6 @@ package com.example; import org.springframework.cloud.gateway.server.mvc.filter.SimpleFilterSupplier; -@Configuration class SampleFilterSupplier extends SimpleFilterSupplier { public SampleFilterSupplier() { @@ -294,7 +308,24 @@ class SampleFilterSupplier extends SimpleFilterSupplier { } ---- -You then need to add the class in `META-INF/spring.factories`. +To register the `FilterSupplier` for use in config files, you then need to add the class as a bean as in the example below: + +.FilterConfiguration.java +[source,java] +---- +package com.example; + +@Configuration +class FilterConfiguration { + + @Bean + public SampleFilterSupplier sampleFilterSupplier() { + return new SampleFilterSupplier(); + } +} +---- + +The requirement to add the class to `META-INF/spring.factories` is deprecated and will be removed in the next major release. .META-INF/spring.factories [source] From 1e02b28499e585a0e157de9ea0c16dd137bf963f Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 17 Apr 2025 11:38:28 -0400 Subject: [PATCH 6/6] Updates for AOT See gh-3250 --- .../mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java | 9 +++++---- .../server/mvc/filter/FilterAutoConfiguration.java | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java index 08a879c1f9..c171c2403f 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java @@ -29,6 +29,7 @@ import org.springframework.cloud.gateway.server.mvc.filter.BodyFilterFunctions; import org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions; import org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions; +import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration; import org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions; import org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions; import org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions; @@ -47,10 +48,10 @@ public class GatewayMvcAotRuntimeHintsRegistrar implements RuntimeHintsRegistrar // TODO: fix AOT HINTS private static final Set> FUNCTION_PROVIDERS = Set.of(HandlerFunctions.class, - /* LoadBalancerHandlerSupplier.class, */ FilterFunctions.class, BeforeFilterFunctions.class, - AfterFilterFunctions.class, TokenRelayFilterFunctions.class, BodyFilterFunctions.class, - CircuitBreakerFilterFunctions.class, GatewayRouterFunctions.class, LoadBalancerFilterFunctions.class, - GatewayRequestPredicates.class, Bucket4jFilterFunctions.class); + FilterAutoConfiguration.LoadBalancerHandlerConfiguration.class, FilterFunctions.class, + BeforeFilterFunctions.class, AfterFilterFunctions.class, TokenRelayFilterFunctions.class, + BodyFilterFunctions.class, CircuitBreakerFilterFunctions.class, GatewayRouterFunctions.class, + LoadBalancerFilterFunctions.class, GatewayRequestPredicates.class, Bucket4jFilterFunctions.class); private static final Set> PROPERTIES = Set.of(FilterProperties.class, PredicateProperties.class, RouteProperties.class); diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java index ad260ccbca..8f4126cf1f 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java @@ -71,7 +71,7 @@ public CircuitBreakerFilterFunctions.FilterSupplier circuitBreakerFilterFunction @Configuration(proxyBeanMethods = false) @ConditionalOnClass(LoadBalancerClient.class) - static class LoadBalancerHandlerConfiguration { + public static class LoadBalancerHandlerConfiguration { @Bean public Function lbHandlerFunctionDefinition() {