Skip to content

Commit a7f3ce2

Browse files
authored
Merge branch 'spring-cloud:main' into main
2 parents 3992039 + 86b4529 commit a7f3ce2

File tree

4 files changed

+197
-88
lines changed

4 files changed

+197
-88
lines changed

docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/java-routes-api.adoc

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,71 @@ class SimpleGateway {
4949
[[gateway-handlerfunctions]]
5050
== Gateway MVC Handler Functions
5151

52-
Various `RouterFunctions.Builder` methods require a `HandlerFunction<ServerResponse>`. To create a route that is proxied by the MVC Gateway, `HandlerFunction` implementations are supplied in `org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions`. The most basic is the `http()` `HandlerFunction`. The function looks for a `URI` in the `org.springframework.cloud.gateway.server.mvc.common.MvcUtils.GATEWAY_REQUEST_URL_ATTR` request attribute. This allows for dynamic targets such as load balancing to set the `URI`.
5352

54-
WARNING: As of version 4.1.7, `HandlerFunctions.http(String)` and `HandlerFunctions.http(URI)` are now deprecated. Please use `HandlerFunctions.http()` in combination with the `BeforeFilterFunctions.uri()` filter instead. This fixes inconsistencies in dealing with the route url request attribute.
53+
Various `RouterFunctions.Builder` methods require a `HandlerFunction<ServerResponse>`. To create a route that is proxied by the MVC Gateway, `HandlerFunction` implementations are supplied in `org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions`.
54+
55+
=== HTTP Handler Function
56+
The most basic handler function is `http()` `HandlerFunction`. If a `URI` is supplied as a parameter, that is the `URI` used as the downstream target for sending the HTTP requests (as seen in the example above). If no parameter is passed, the function looks for a `URI` in the `org.springframework.cloud.gateway.server.mvc.common.MvcUtils.GATEWAY_REQUEST_URL_ATTR` request attribute. This allows for dynamic targets such as load balancing to set the `URI`.
57+
58+
59+
WARNING: As of version 4.1.7, `HandlerFunctions.http(String)` and `HandlerFunctions.http(URI)` are now deprecated. Please use `HandlerFunctions.http()` in combination with the `BeforeFilterFunctions.uri()` filter instead. This fixes inconsistencies in dealing with the route url request attribute.
60+
61+
=== Spring Cloud Function Handler Function
62+
By placing https://spring.io/projects/spring-cloud-function[Spring Cloud Function] on the classpath, Spring Cloud Gateway will automatically configure routes to invoke functions you define as beans. The bean names of the functions will be used as the path of the routes.
63+
64+
For example, given the following configuration:
65+
66+
[source,xml]
67+
----
68+
<dependency>
69+
<groupId>org.springframework.cloud</groupId>
70+
<artifactId>spring-cloud-function-context</artifactId>
71+
</dependency>
72+
----
73+
74+
Once Spring Cloud Function dependency is provided the name of the Java function bean becomes the path you can use to route to functions.
75+
76+
For example, assume the following application:
77+
78+
[source,java]
79+
----
80+
@SpringBootApplication
81+
public class DemoFunctionGatewayApplication {
82+
83+
public static void main(String[] args) {
84+
SpringApplication.run(DemoFunctionGatewayApplication.class, args);
85+
}
86+
87+
88+
@Bean
89+
public Function<String, String> uppercase() {
90+
return v -> v.toUpperCase();
91+
}
92+
93+
@Bean
94+
public Function<String, String> concat() {
95+
return v -> v + v;
96+
}
97+
}
98+
----
99+
You can invoke the `concat` or `uppercase` functions by issuing a `GET` or `POST` request to `/concat` or `/uppercase`.
100+
101+
Making a `GET` request to ``http://localhost:8080/uppercase/hello` will invoke the `uppercase` function with the String `hello` and return `HELLO` in the `GET` response body.
102+
103+
Instead of passing the function parameter as a path parameter you can use a `POST` request. For example the following cURL command can issued to invoke the `concat` function:
104+
105+
[source,bash]
106+
----
107+
$ curl -d ‘"hello"' -H "Content-Type: application/json" -X POST http://localhost:8080/concat
108+
----
109+
110+
The response body will contain `hellohello`.
111+
112+
Spring Cloud Gateway also supports function composition by issuing a request to a path composed of function names separated by a comma. For example:
113+
114+
[source,bash]
115+
----
116+
$ curl -d ‘"hello"' -H "Content-Type: application/json" -X POST http://localhost:8080/concat,uppercase
117+
----
118+
119+
The response body will contain `HELLOHELLO`.

spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
import org.springframework.boot.http.client.HttpRedirects;
3333
import org.springframework.boot.web.client.RestClientCustomizer;
3434
import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplierBeanPostProcessor;
35-
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcAotRuntimeHintsRegistrar;
3635
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
3736
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcPropertiesBeanDefinitionRegistrar;
37+
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcRuntimeHintsProcessor;
3838
import org.springframework.cloud.gateway.server.mvc.config.RouterFunctionHolderFactory;
3939
import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration;
4040
import org.springframework.cloud.gateway.server.mvc.filter.FilterBeanFactoryDiscoverer;
@@ -60,7 +60,6 @@
6060
import org.springframework.context.ApplicationEventPublisher;
6161
import org.springframework.context.annotation.Bean;
6262
import org.springframework.context.annotation.Import;
63-
import org.springframework.context.annotation.ImportRuntimeHints;
6463
import org.springframework.core.env.ConfigurableEnvironment;
6564
import org.springframework.core.env.Environment;
6665
import org.springframework.core.env.MapPropertySource;
@@ -80,7 +79,6 @@
8079
PredicateAutoConfiguration.class })
8180
@ConditionalOnProperty(name = GatewayMvcProperties.PREFIX + ".enabled", matchIfMissing = true)
8281
@Import(GatewayMvcPropertiesBeanDefinitionRegistrar.class)
83-
@ImportRuntimeHints(GatewayMvcAotRuntimeHintsRegistrar.class)
8482
public class GatewayServerMvcAutoConfiguration {
8583

8684
@Bean
@@ -209,6 +207,11 @@ public XForwardedRequestHeadersFilterProperties xForwardedRequestHeadersFilterPr
209207
return new XForwardedRequestHeadersFilterProperties();
210208
}
211209

210+
@Bean
211+
static GatewayMvcRuntimeHintsProcessor gatewayMvcRuntimeHintsProcessor() {
212+
return new GatewayMvcRuntimeHintsProcessor();
213+
}
214+
212215
static class GatewayHttpClientEnvironmentPostProcessor implements EnvironmentPostProcessor {
213216

214217
static final boolean APACHE = ClassUtils.isPresent("org.apache.hc.client5.http.impl.classic.HttpClients", null);

spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java

Lines changed: 0 additions & 83 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gateway.server.mvc.config;
18+
19+
import java.util.Collections;
20+
import java.util.HashSet;
21+
import java.util.Map;
22+
import java.util.Set;
23+
import java.util.stream.Collectors;
24+
import java.util.stream.Stream;
25+
26+
import org.apache.commons.logging.Log;
27+
import org.apache.commons.logging.LogFactory;
28+
29+
import org.springframework.aot.hint.MemberCategory;
30+
import org.springframework.aot.hint.ReflectionHints;
31+
import org.springframework.aot.hint.TypeReference;
32+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
33+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
34+
import org.springframework.beans.factory.config.BeanDefinition;
35+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
36+
import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration;
37+
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateAutoConfiguration;
38+
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
39+
import org.springframework.core.type.filter.AssignableTypeFilter;
40+
41+
/**
42+
* A {@link BeanFactoryInitializationAotProcessor} responsible for registering reflection
43+
* hints for Gateway MVC beans.
44+
*
45+
* @author Jürgen Wißkirchen
46+
* @author Olga Maciaszek-Sharma
47+
* @since 4.3.0
48+
*/
49+
public class GatewayMvcRuntimeHintsProcessor implements BeanFactoryInitializationAotProcessor {
50+
51+
private static final Log LOG = LogFactory.getLog(GatewayMvcRuntimeHintsProcessor.class);
52+
53+
private static final String GATEWAY_MVC_FILTER_PACKAGE_NAME = "org.springframework.cloud.gateway.server.mvc.filter";
54+
55+
private static final String GATEWAY_MVC_PREDICATE_PACKAGE_NAME = "org.springframework.cloud.gateway.server.mvc.predicate";
56+
57+
private static final Map<String, Set<String>> beansConditionalOnClasses = Map.of(
58+
"io.github.bucket4j.BucketConfiguration",
59+
Set.of("org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions"),
60+
"org.springframework.cloud.client.circuitbreaker.CircuitBreaker",
61+
Set.of("org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions"),
62+
"org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient",
63+
Set.of("org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions"),
64+
"org.springframework.retry.support.RetryTemplate",
65+
Set.of("org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions"),
66+
"org.springframework.security.oauth2.client.OAuth2AuthorizedClient",
67+
Set.of("org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions"));
68+
69+
private static final Set<Class<?>> PROPERTIES = Set.of(FilterProperties.class, PredicateProperties.class,
70+
RouteProperties.class);
71+
72+
@Override
73+
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
74+
return (generationContext, beanFactoryInitializationCode) -> {
75+
ReflectionHints hints = generationContext.getRuntimeHints().reflection();
76+
Set<Class<?>> typesToRegister = Stream
77+
.of(getTypesToRegister(GATEWAY_MVC_FILTER_PACKAGE_NAME),
78+
getTypesToRegister(GATEWAY_MVC_PREDICATE_PACKAGE_NAME), PROPERTIES)
79+
.flatMap(Set::stream)
80+
.collect(Collectors.toSet());
81+
typesToRegister.forEach(clazz -> hints.registerType(TypeReference.of(clazz),
82+
hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS,
83+
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)));
84+
};
85+
}
86+
87+
private static Set<Class<?>> getTypesToRegister(String packageName) {
88+
Set<Class<?>> classesToAdd = new HashSet<>();
89+
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
90+
provider.addIncludeFilter(new AssignableTypeFilter(Object.class));
91+
provider.addExcludeFilter(new AssignableTypeFilter(FilterAutoConfiguration.class));
92+
provider.addExcludeFilter(new AssignableTypeFilter(PredicateAutoConfiguration.class));
93+
Set<BeanDefinition> components = provider.findCandidateComponents(packageName);
94+
for (BeanDefinition component : components) {
95+
Class<?> clazz;
96+
try {
97+
clazz = Class.forName(component.getBeanClassName());
98+
if (shouldRegisterClass(clazz)) {
99+
classesToAdd.add(clazz);
100+
}
101+
}
102+
catch (NoClassDefFoundError | ClassNotFoundException exception) {
103+
if (LOG.isDebugEnabled()) {
104+
LOG.debug(exception);
105+
}
106+
}
107+
}
108+
return classesToAdd;
109+
}
110+
111+
private static boolean shouldRegisterClass(Class<?> clazz) {
112+
Set<String> conditionClasses = beansConditionalOnClasses.getOrDefault(clazz.getName(), Collections.emptySet());
113+
for (String conditionClass : conditionClasses) {
114+
try {
115+
GatewayMvcRuntimeHintsProcessor.class.getClassLoader().loadClass(conditionClass);
116+
}
117+
catch (ClassNotFoundException e) {
118+
return false;
119+
}
120+
}
121+
return true;
122+
}
123+
124+
}

0 commit comments

Comments
 (0)