Skip to content

Commit d11df4b

Browse files
committed
Merge origin/4.1.x
Signed-off-by: Olga Maciaszek-Sharma <olga.maciaszek-sharma@broadcom.com>
2 parents 34f38ea + d349a9f commit d11df4b

File tree

6 files changed

+174
-131
lines changed

6 files changed

+174
-131
lines changed

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
@@ -31,9 +31,9 @@
3131
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
3232
import org.springframework.boot.web.client.RestClientCustomizer;
3333
import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplierBeanPostProcessor;
34-
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcAotRuntimeHintsRegistrar;
3534
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
3635
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcPropertiesBeanDefinitionRegistrar;
36+
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcRuntimeHintsProcessor;
3737
import org.springframework.cloud.gateway.server.mvc.config.RouterFunctionHolderFactory;
3838
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
3939
import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter;
@@ -54,7 +54,6 @@
5454
import org.springframework.context.ApplicationEventPublisher;
5555
import org.springframework.context.annotation.Bean;
5656
import org.springframework.context.annotation.Import;
57-
import org.springframework.context.annotation.ImportRuntimeHints;
5857
import org.springframework.core.env.ConfigurableEnvironment;
5958
import org.springframework.core.env.Environment;
6059
import org.springframework.core.env.MapPropertySource;
@@ -73,7 +72,6 @@
7372
RestClientAutoConfiguration.class })
7473
@ConditionalOnProperty(name = "spring.cloud.gateway.mvc.enabled", matchIfMissing = true)
7574
@Import(GatewayMvcPropertiesBeanDefinitionRegistrar.class)
76-
@ImportRuntimeHints(GatewayMvcAotRuntimeHintsRegistrar.class)
7775
public class GatewayServerMvcAutoConfiguration {
7876

7977
@Bean
@@ -199,6 +197,11 @@ public XForwardedRequestHeadersFilterProperties xForwardedRequestHeadersFilterPr
199197
return new XForwardedRequestHeadersFilterProperties();
200198
}
201199

200+
@Bean
201+
static GatewayMvcRuntimeHintsProcessor gatewayMvcRuntimeHintsProcessor() {
202+
return new GatewayMvcRuntimeHintsProcessor();
203+
}
204+
202205
static class GatewayHttpClientEnvironmentPostProcessor implements EnvironmentPostProcessor {
203206

204207
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 & 82 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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.context.annotation.ClassPathScanningCandidateComponentProvider;
37+
import org.springframework.core.type.filter.AssignableTypeFilter;
38+
39+
/**
40+
* A {@link BeanFactoryInitializationAotProcessor} responsible for registering reflection
41+
* hints for Gateway MVC beans.
42+
*
43+
* @author Jürgen Wißkirchen
44+
* @author Olga Maciaszek-Sharma
45+
* @since 4.3.0
46+
*/
47+
public class GatewayMvcRuntimeHintsProcessor implements BeanFactoryInitializationAotProcessor {
48+
49+
private static final Log LOG = LogFactory.getLog(GatewayMvcRuntimeHintsProcessor.class);
50+
51+
private static final String GATEWAY_MVC_FILTER_PACKAGE_NAME = "org.springframework.cloud.gateway.server.mvc.filter";
52+
53+
private static final String GATEWAY_MVC_PREDICATE_PACKAGE_NAME = "org.springframework.cloud.gateway.server.mvc.predicate";
54+
55+
private static final Map<String, Set<String>> beansConditionalOnClasses = Map.of(
56+
"io.github.bucket4j.BucketConfiguration",
57+
Set.of("org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions"),
58+
"org.springframework.cloud.client.circuitbreaker.CircuitBreaker",
59+
Set.of("org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions"),
60+
"org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient",
61+
Set.of("org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions"),
62+
"org.springframework.retry.support.RetryTemplate",
63+
Set.of("org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions"),
64+
"org.springframework.security.oauth2.client.OAuth2AuthorizedClient",
65+
Set.of("org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions"));
66+
67+
private static final Set<Class<?>> PROPERTIES = Set.of(FilterProperties.class, PredicateProperties.class,
68+
RouteProperties.class);
69+
70+
@Override
71+
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
72+
return (generationContext, beanFactoryInitializationCode) -> {
73+
ReflectionHints hints = generationContext.getRuntimeHints().reflection();
74+
Set<Class<?>> typesToRegister = Stream
75+
.of(getTypesToRegister(GATEWAY_MVC_FILTER_PACKAGE_NAME),
76+
getTypesToRegister(GATEWAY_MVC_PREDICATE_PACKAGE_NAME), PROPERTIES)
77+
.flatMap(Set::stream)
78+
.collect(Collectors.toSet());
79+
typesToRegister.forEach(clazz -> hints.registerType(TypeReference.of(clazz),
80+
hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS,
81+
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)));
82+
};
83+
}
84+
85+
private static Set<Class<?>> getTypesToRegister(String packageName) {
86+
Set<Class<?>> classesToAdd = new HashSet<>();
87+
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
88+
provider.addIncludeFilter(new AssignableTypeFilter(Object.class));
89+
Set<BeanDefinition> components = provider.findCandidateComponents(packageName);
90+
for (BeanDefinition component : components) {
91+
Class<?> clazz;
92+
try {
93+
clazz = Class.forName(component.getBeanClassName());
94+
if (shouldRegisterClass(clazz)) {
95+
classesToAdd.add(clazz);
96+
}
97+
}
98+
catch (NoClassDefFoundError | ClassNotFoundException exception) {
99+
if (LOG.isDebugEnabled()) {
100+
LOG.debug(exception);
101+
}
102+
}
103+
}
104+
return classesToAdd;
105+
}
106+
107+
private static boolean shouldRegisterClass(Class<?> clazz) {
108+
Set<String> conditionClasses = beansConditionalOnClasses.getOrDefault(clazz.getName(), Collections.emptySet());
109+
for (String conditionClass : conditionClasses) {
110+
try {
111+
GatewayMvcRuntimeHintsProcessor.class.getClassLoader().loadClass(conditionClass);
112+
}
113+
catch (ClassNotFoundException e) {
114+
return false;
115+
}
116+
}
117+
return true;
118+
}
119+
120+
}

spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/AfterFilterFunctionsTests.java

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -60,58 +60,58 @@ class AfterFilterFunctionsTests {
6060
@Test
6161
void doesNotRemoveJsonAttributes() {
6262
restClient.get()
63-
.uri("/anything/does_not/remove_json_attributes")
64-
.exchange()
65-
.expectStatus()
66-
.isOk()
67-
.expectBody(Map.class)
68-
.consumeWith(res -> {
69-
assertThat(res.getResponseBody()).containsEntry("foo", "bar");
70-
assertThat(res.getResponseBody()).containsEntry("baz", "qux");
71-
});
63+
.uri("/anything/does_not/remove_json_attributes")
64+
.exchange()
65+
.expectStatus()
66+
.isOk()
67+
.expectBody(Map.class)
68+
.consumeWith(res -> {
69+
assertThat(res.getResponseBody()).containsEntry("foo", "bar");
70+
assertThat(res.getResponseBody()).containsEntry("baz", "qux");
71+
});
7272
}
7373

7474
@Test
7575
void removeJsonAttributesToAvoidBeingRecursive() {
7676
restClient.get()
77-
.uri("/anything/remove_json_attributes_to_avoid_being_recursive")
78-
.exchange()
79-
.expectStatus()
80-
.isOk()
81-
.expectBody(Map.class)
82-
.consumeWith(res -> {
83-
assertThat(res.getResponseBody()).doesNotContainKey("foo");
84-
assertThat(res.getResponseBody()).containsEntry("baz", "qux");
85-
});
77+
.uri("/anything/remove_json_attributes_to_avoid_being_recursive")
78+
.exchange()
79+
.expectStatus()
80+
.isOk()
81+
.expectBody(Map.class)
82+
.consumeWith(res -> {
83+
assertThat(res.getResponseBody()).doesNotContainKey("foo");
84+
assertThat(res.getResponseBody()).containsEntry("baz", "qux");
85+
});
8686
}
8787

8888
@Test
8989
void removeJsonAttributesRecursively() {
9090
restClient.get()
91-
.uri("/anything/remove_json_attributes_recursively")
92-
.exchange()
93-
.expectStatus()
94-
.isOk()
95-
.expectBody(Map.class)
96-
.consumeWith(res -> {
97-
assertThat(res.getResponseBody()).containsKey("foo");
98-
assertThat((Map<String, String>) res.getResponseBody().get("foo")).containsEntry("bar", "A");
99-
assertThat(res.getResponseBody()).containsEntry("quux", "C");
100-
assertThat(res.getResponseBody()).doesNotContainKey("qux");
101-
});
91+
.uri("/anything/remove_json_attributes_recursively")
92+
.exchange()
93+
.expectStatus()
94+
.isOk()
95+
.expectBody(Map.class)
96+
.consumeWith(res -> {
97+
assertThat(res.getResponseBody()).containsKey("foo");
98+
assertThat((Map<String, String>) res.getResponseBody().get("foo")).containsEntry("bar", "A");
99+
assertThat(res.getResponseBody()).containsEntry("quux", "C");
100+
assertThat(res.getResponseBody()).doesNotContainKey("qux");
101+
});
102102
}
103103

104104
@Test
105105
void raisedErrorWhenRemoveJsonAttributes() {
106106
restClient.get()
107-
.uri("/anything/raised_error_when_remove_json_attributes")
108-
.exchange()
109-
.expectStatus()
110-
.is5xxServerError()
111-
.expectBody(String.class)
112-
.consumeWith(res -> {
113-
assertThat(res.getResponseBody()).isEqualTo("Failed to process JSON of response body.");
114-
});
107+
.uri("/anything/raised_error_when_remove_json_attributes")
108+
.exchange()
109+
.expectStatus()
110+
.is5xxServerError()
111+
.expectBody(String.class)
112+
.consumeWith(res -> {
113+
assertThat(res.getResponseBody()).isEqualTo("Failed to process JSON of response body.");
114+
});
115115
}
116116

117117
@SpringBootConfiguration

spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ public void pathRoutePredicateVisitWithSetWebfluxBasePath() {
9191

9292
PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(webFluxProperties);
9393
PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config()
94-
.setPatterns(List.of("/temp/**"))
95-
.setMatchTrailingSlash(true);
94+
.setPatterns(List.of("/temp/**"))
95+
.setMatchTrailingSlash(true);
9696

9797
Predicate<ServerWebExchange> predicate = pathRoutePredicateFactory.apply(config);
9898

99-
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test")
100-
.build());
99+
ServerWebExchange exchange = MockServerWebExchange
100+
.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test").build());
101101

102102
assertThat(predicate.test(exchange)).isEqualTo(true);
103103
}
@@ -109,13 +109,13 @@ public void pathRoutePredicateVisitWithSetWebfluxBasePathStripPrefix() {
109109

110110
PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(webFluxProperties);
111111
PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config()
112-
.setPatterns(List.of("/temp/**"))
113-
.setMatchTrailingSlash(true);
112+
.setPatterns(List.of("/temp/**"))
113+
.setMatchTrailingSlash(true);
114114

115115
Predicate<ServerWebExchange> predicate = pathRoutePredicateFactory.apply(config);
116116

117-
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test")
118-
.build());
117+
ServerWebExchange exchange = MockServerWebExchange
118+
.from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test").build());
119119

120120
assertThat(predicate.test(exchange)).isEqualTo(true);
121121

@@ -139,4 +139,5 @@ public void pathRoutePredicateVisitWithSetWebfluxBasePathStripPrefix() {
139139
LinkedHashSet<URI> uris = webExchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
140140
assertThat(uris).contains(exchange.getRequest().getURI());
141141
}
142+
142143
}

spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ public class PathRoutePredicatePathContainerAttrBenchMarkTests {
5555
PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config()
5656
.setPatterns(Collections.singletonList(PATH_PATTERN_PREFIX + i))
5757
.setMatchTrailingSlash(true);
58-
Predicate<ServerWebExchange> predicate = new PathRoutePredicateFactory(new WebFluxProperties()).apply(config);
58+
Predicate<ServerWebExchange> predicate = new PathRoutePredicateFactory(new WebFluxProperties())
59+
.apply(config);
5960
predicates.add(predicate);
6061
}
6162
}

0 commit comments

Comments
 (0)