Allow bean registration of server-webmcv components

This replaces the spring.factories mechanism.

Fixes gh-3250
This commit is contained in:
spencergibb
2025-04-14 17:40:19 -04:00
parent ec07cb8579
commit 51548b07c8
9 changed files with 222 additions and 166 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.gateway.server.mvc;
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.GatewayMvcAotRuntimeH
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.TransferEncodingNorma
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 @@ import org.springframework.web.client.RestClient;
* @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 class GatewayServerMvcAutoConfiguration {
}
@Bean
public RouterFunctionHolderFactory routerFunctionHolderFactory(Environment env) {
return new RouterFunctionHolderFactory(env);
public RouterFunctionHolderFactory routerFunctionHolderFactory(Environment env, BeanFactory beanFactory) {
return new RouterFunctionHolderFactory(env, beanFactory);
}
@Bean

View File

@@ -31,7 +31,6 @@ import org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctio
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 @@ import org.springframework.util.ClassUtils;
*/
public class GatewayMvcAotRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
// TODO: fix AOT HINTS
private static final Set<Class<?>> 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);

View File

@@ -28,10 +28,14 @@ import java.util.Map;
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.Configurable;
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 class RouterFunctionHolderFactory {
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 @@ public class RouterFunctionHolderFactory {
// TODO: cache?
// translate handlerFunction
String scheme = routeProperties.getUri().getScheme();
Map<String, Object> handlerArgs = new HashMap<>();
Optional<NormalizedOperationMethod> 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<ServerResponse> handlerFunction = null;
// filters added by HandlerDiscoverer need to go last, so save them
HandlerFunction<ServerResponse> handlerFunction = null;
List<HandlerFilterFunction<ServerResponse, ServerResponse>> lowerPrecedenceFilters = new ArrayList<>();
List<HandlerFilterFunction<ServerResponse, ServerResponse>> higherPrecedenceFilters = new ArrayList<>();
if (response instanceof HandlerFunction<?>) {
handlerFunction = (HandlerFunction<ServerResponse>) 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<String, Object> handlerArgs = new HashMap<>();
Optional<NormalizedOperationMethod> 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<ServerResponse>) 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

View File

@@ -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<Method> 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<RouteProperties, HandlerFunctionDefinition> 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())));
}
}

View File

@@ -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<Method> 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<ServerResponse> 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<ServerResponse> handlerFunction) {
HandlerFilterFunction<ServerResponse, ServerResponse> setId = setIdFilter(id);
HandlerFilterFunction<ServerResponse, ServerResponse> setRequest = setRequestUrlFilter(uri);
return new HandlerDiscoverer.Result(handlerFunction, Arrays.asList(setId, setRequest), Collections.emptyList());
}
private static HandlerFilterFunction<ServerResponse, ServerResponse> setIdFilter(String id) {
return (request, next) -> {
MvcUtils.setRouteId(request, id);
return next.handle(request);
};
}
private static HandlerFilterFunction<ServerResponse, ServerResponse> setRequestUrlFilter(URI uri) {
return (request, next) -> {
MvcUtils.setRequestUrl(request, uri);
return next.handle(request);
};
}
}

View File

@@ -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<RouteProperties, HandlerFunctionDefinition> fnHandlerFunctionDefinition() {
return routeProperties -> new HandlerFunctionDefinition.Default("fn",
HandlerFunctions.fn(routeProperties.getUri().getSchemeSpecificPart()));
}
@Bean
public Function<RouteProperties, HandlerFunctionDefinition> forwardHandlerFunctionDefinition() {
return routeProperties -> new HandlerFunctionDefinition.Default("forward",
HandlerFunctions.forward(routeProperties.getUri().getPath()));
}
@Bean
public Function<RouteProperties, HandlerFunctionDefinition> httpHandlerFunctionDefinition() {
return routeProperties -> getResult("http", routeProperties.getId(), routeProperties.getUri(),
HandlerFunctions.http());
}
@Bean
public Function<RouteProperties, HandlerFunctionDefinition> httpsHandlerFunctionDefinition() {
return routeProperties -> getResult("https", routeProperties.getId(), routeProperties.getUri(),
HandlerFunctions.https());
}
@Bean
public Function<RouteProperties, HandlerFunctionDefinition> noHandlerFunctionDefinition() {
return routeProperties -> getResult("no", routeProperties.getId(), routeProperties.getUri(),
HandlerFunctions.no());
}
@Bean
public Function<RouteProperties, HandlerFunctionDefinition> streamHandlerFunctionDefinition() {
return routeProperties -> new HandlerFunctionDefinition.Default("stream",
HandlerFunctions.stream(routeProperties.getUri().getSchemeSpecificPart()));
}
private static HandlerFunctionDefinition getResult(String scheme, String id, URI uri,
HandlerFunction<ServerResponse> handlerFunction) {
HandlerFilterFunction<ServerResponse, ServerResponse> setId = setIdFilter(id);
HandlerFilterFunction<ServerResponse, ServerResponse> setRequest = setRequestUrlFilter(uri);
return new HandlerFunctionDefinition.Default(scheme, handlerFunction, Arrays.asList(setId, setRequest),
Collections.emptyList());
}
private static HandlerFilterFunction<ServerResponse, ServerResponse> setIdFilter(String id) {
return (request, next) -> {
MvcUtils.setRouteId(request, id);
return next.handle(request);
};
}
private static HandlerFilterFunction<ServerResponse, ServerResponse> setRequestUrlFilter(URI uri) {
return (request, next) -> {
MvcUtils.setRequestUrl(request, uri);
return next.handle(request);
};
}
}

View File

@@ -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<ServerResponse> handlerFunction();
List<HandlerFilterFunction<ServerResponse, ServerResponse>> lowerPrecedenceFilters();
List<HandlerFilterFunction<ServerResponse, ServerResponse>> higherPrecedenceFilters();
record Default(String scheme, HandlerFunction<ServerResponse> handlerFunction,
List<HandlerFilterFunction<ServerResponse, ServerResponse>> lowerPrecedenceFilters,
List<HandlerFilterFunction<ServerResponse, ServerResponse>> higherPrecedenceFilters)
implements
HandlerFunctionDefinition {
public Default(String scheme, HandlerFunction<ServerResponse> handlerFunction) {
this(scheme, handlerFunction, Collections.emptyList(), Collections.emptyList());
}
}
}

View File

@@ -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

View File

@@ -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