Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit fbb593e

Browse files
Support bean context for operation method invocation
Signed-off-by: YeonHo Ju <nerdroid@outlook.kr>
1 parent a19d4ea commit fbb593e

File tree

3 files changed

+207
-2
lines changed

3 files changed

+207
-2
lines changed

‎spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java‎

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.cloud.gateway.server.mvc.config;
1818

19+
import java.lang.reflect.Modifier;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.Collections;
@@ -30,12 +31,14 @@
3031
import java.util.function.Consumer;
3132
import java.util.function.Function;
3233

34+
import jakarta.annotation.Nullable;
3335
import org.apache.commons.logging.Log;
3436
import org.apache.commons.logging.LogFactory;
3537

3638
import org.springframework.beans.factory.BeanFactory;
3739
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
3840
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
41+
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
3942
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
4043
import org.springframework.boot.context.properties.bind.Bindable;
4144
import org.springframework.boot.context.properties.bind.Binder;
@@ -351,12 +354,38 @@ private <T> T invokeOperation(OperationMethod operationMethod, Map<String, Objec
351354
else {
352355
args.putAll(operationArgs);
353356
}
354-
ReflectiveOperationInvoker operationInvoker = new ReflectiveOperationInvoker(operationMethod,
355-
this.parameterValueMapper);
357+
358+
ReflectiveOperationInvoker operationInvoker = new ReflectiveOperationInvoker(
359+
resolveInvocationTargetBean(operationMethod), operationMethod, this.parameterValueMapper);
356360
InvocationContext context = new InvocationContext(args, trueNullOperationArgumentResolver);
357361
return operationInvoker.invoke(context);
358362
}
359363

364+
@Nullable
365+
private Object resolveInvocationTargetBean(OperationMethod operationMethod) {
366+
// if the method is static, we don't have to find the invocation target bean
367+
if (Modifier.isStatic(operationMethod.getMethod().getModifiers())) {
368+
return null;
369+
}
370+
371+
try {
372+
if (beanFactory != null) {
373+
return beanFactory.getBean(operationMethod.getMethod().getDeclaringClass());
374+
}
375+
}
376+
catch (NoUniqueBeanDefinitionException e) {
377+
log.warn(LogMessage.format(
378+
"Multiple beans found for type [%s], this non-static operation method [%s] will be invoked without bean context",
379+
operationMethod.getMethod().getDeclaringClass().getName(), operationMethod.getMethod().getName()));
380+
}
381+
catch (NoSuchBeanDefinitionException e) {
382+
log.debug(LogMessage.format(
383+
"No bean registered for type [%s], this non-static operation method [%s] will be invoked without bean context",
384+
operationMethod.getMethod().getDeclaringClass().getName(), operationMethod.getMethod().getName()));
385+
}
386+
return null;
387+
}
388+
360389
private Object bindConfigurable(OperationMethod operationMethod, Map<String, Object> args,
361390
OperationParameter operationParameter) {
362391
Class<?> configurableType = operationParameter.getType();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Copyright 2013-present 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.Map;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.beans.factory.BeanNameAware;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.beans.factory.annotation.Qualifier;
26+
import org.springframework.boot.SpringBootConfiguration;
27+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
28+
import org.springframework.boot.test.context.SpringBootTest;
29+
import org.springframework.cloud.gateway.server.mvc.common.Configurable;
30+
import org.springframework.cloud.gateway.server.mvc.filter.SimpleFilterSupplier;
31+
import org.springframework.cloud.gateway.server.mvc.test.HttpbinTestcontainers;
32+
import org.springframework.cloud.gateway.server.mvc.test.PermitAllSecurityConfiguration;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Import;
35+
import org.springframework.test.context.ActiveProfiles;
36+
import org.springframework.test.context.ContextConfiguration;
37+
import org.springframework.test.web.servlet.client.RestTestClient;
38+
import org.springframework.web.servlet.function.HandlerFilterFunction;
39+
import org.springframework.web.servlet.function.ServerRequest;
40+
import org.springframework.web.servlet.function.ServerResponse;
41+
42+
import static org.assertj.core.api.Assertions.assertThat;
43+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
44+
import static org.springframework.cloud.gateway.server.mvc.test.TestUtils.getMap;
45+
46+
@SpringBootTest(webEnvironment = RANDOM_PORT)
47+
@ActiveProfiles("beancontextoperationmethod")
48+
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
49+
class BeanContextOperationMethodTests {
50+
private static final String X_CONFIGURED_ARGUMENT = "X-Configured-Argument";
51+
private static final String X_CUSTOM_FILTER_BEAN_NAME = "X-Custom-Filter-Bean-Name";
52+
private static final String X_CUSTOM_FILTER_SERVICE_BEAN_NAME = "X-Custom-Filter-Service-Bean-Name";
53+
private static final String X_CUSTOM_FILTER_SERVICE_CALL_COUNT = "X-Custom-Filter-Service-Call-Count";
54+
55+
private static final String X_CUSTOM_FILTER_BEAN_NAME_VALUE = "bean-context-operation-method-test-filter";
56+
private static final String X_CUSTOM_FILTER_SERVICE_BEAN_NAME_VALUE = "bean-context-operation-method-test-filter-service";
57+
58+
@Autowired
59+
RestTestClient restClient;
60+
61+
@Test
62+
void nonStaticOperationMethodUsesBean() {
63+
// Given: CustomBeanFilterFilterFunctions is registered as a bean
64+
// When: Route uses CustomBeanFilter which is a non-static method
65+
for (int i = 1; i <= 3; i++) {
66+
String callCount = "" + i;
67+
restClient.get()
68+
.uri("/anything/bean-context")
69+
.exchange()
70+
.expectStatus()
71+
.isOk()
72+
.expectBody(Map.class)
73+
.consumeWith(res -> {
74+
Map<String, Object> headers = getMap(res.getResponseBody(), "headers");
75+
assertThat(headers).containsEntry(X_CUSTOM_FILTER_BEAN_NAME, X_CUSTOM_FILTER_BEAN_NAME_VALUE);
76+
assertThat(headers).containsEntry(X_CUSTOM_FILTER_SERVICE_BEAN_NAME, X_CUSTOM_FILTER_SERVICE_BEAN_NAME_VALUE);
77+
assertThat(headers).containsEntry(X_CUSTOM_FILTER_SERVICE_CALL_COUNT, callCount);
78+
assertThat(headers).containsKey(X_CONFIGURED_ARGUMENT);
79+
});
80+
}
81+
}
82+
83+
@SpringBootConfiguration
84+
@EnableAutoConfiguration
85+
@Import(PermitAllSecurityConfiguration.class)
86+
protected static class TestConfiguration {
87+
@Bean(X_CUSTOM_FILTER_SERVICE_BEAN_NAME_VALUE)
88+
public CustomFilterService customFilterService() {
89+
return new CustomFilterService();
90+
}
91+
92+
@Bean(X_CUSTOM_FILTER_BEAN_NAME_VALUE)
93+
public CustomBeanFilterFunctions customBeanFilterFunctions(
94+
@Qualifier(X_CUSTOM_FILTER_SERVICE_BEAN_NAME_VALUE) CustomFilterService customFilterService
95+
) {
96+
return new CustomBeanFilterFunctions(customFilterService);
97+
}
98+
}
99+
100+
static class CustomFilterService implements BeanNameAware {
101+
private String beanName;
102+
private int callCount = 0;
103+
104+
@Override
105+
public void setBeanName(String name) {
106+
this.beanName = name;
107+
}
108+
109+
ServerRequest process(ServerRequest request) {
110+
callCount++;
111+
return ServerRequest.from(request)
112+
.header(X_CUSTOM_FILTER_SERVICE_BEAN_NAME, beanName)
113+
.header(X_CUSTOM_FILTER_SERVICE_CALL_COUNT, "" + callCount)
114+
.build();
115+
}
116+
}
117+
118+
static class CustomBeanFilterFunctions extends SimpleFilterSupplier implements BeanNameAware {
119+
private final CustomFilterService service;
120+
private String beanName;
121+
122+
@Override
123+
public void setBeanName(String name) {
124+
this.beanName = name;
125+
}
126+
127+
public CustomBeanFilterFunctions(CustomFilterService service) {
128+
super(CustomBeanFilterFunctions.class);
129+
this.service = service;
130+
}
131+
132+
@Configurable
133+
public HandlerFilterFunction<ServerResponse, ServerResponse> customBeanFilter(Config config) {
134+
return (request, next) ->
135+
{
136+
ServerRequest serviceProcessed = service.process(request);
137+
ServerRequest filterProcessed = ServerRequest.from(serviceProcessed)
138+
.header(X_CUSTOM_FILTER_BEAN_NAME, beanName)
139+
.header(X_CONFIGURED_ARGUMENT, config.getConfiguredArgument())
140+
.build();
141+
return next.handle(filterProcessed);
142+
};
143+
}
144+
145+
static class Config {
146+
private final String configuredArgument;
147+
148+
public Config(String configuredArgument) {
149+
this.configuredArgument = configuredArgument;
150+
}
151+
152+
public String getConfiguredArgument() {
153+
return configuredArgument;
154+
}
155+
}
156+
}
157+
}
158+
159+
160+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
spring.cloud.gateway.server.webmvc.routes:
2+
- id: bean_context_test
3+
uri: https://httpbin.org
4+
predicates:
5+
- name: Path
6+
args:
7+
pattern: /anything/bean-context
8+
filters:
9+
- name: HttpbinUriResolver
10+
- name: CustomBeanFilter
11+
args:
12+
configured-argument: value
13+
14+
logging:
15+
level:
16+
org.springframework.cloud.gateway.server.mvc: TRACE

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /