Skip to content

Commit d289a3b

Browse files
snicollpull[bot]
authored andcommitted
Add qualified injection points for MVC and WebFlux infrastructure
Previously, the infrastructure provided by WebMvcConfigurationSupport and WebFluxConfigurationSupport can lead to unexpected results due to the lack of qualifier for certain dependencies. Those configuration classes refer to very specific beans, yet their injection points do not define such qualifiers. As a result, if a candidate exists for the requested type, the context will inject the existing bean and will ignore a most specific one as such constraint it not defined. This can be easily reproduced by having a primary Validator whereas a dedicated "mvcValidator" is expected. Note that a parameter name is in no way a constraint as the name is only used as a fallback when a single candidate cannot be determined. This commit provides explicit @qualifier metadata for such injection points, renaming the parameter name in the process to clarify that it isn't relevant for the proper bean to be resolved by the context. Closes spring-projectsgh-23887
1 parent 7261f31 commit d289a3b

File tree

4 files changed

+460
-49
lines changed

4 files changed

+460
-49
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import org.springframework.beans.BeanUtils;
2626
import org.springframework.beans.factory.BeanInitializationException;
27+
import org.springframework.beans.factory.annotation.Qualifier;
2728
import org.springframework.context.ApplicationContext;
2829
import org.springframework.context.ApplicationContextAware;
2930
import org.springframework.context.annotation.Bean;
@@ -120,10 +121,10 @@ public WebExceptionHandler responseStatusExceptionHandler() {
120121

121122
@Bean
122123
public RequestMappingHandlerMapping requestMappingHandlerMapping(
123-
RequestedContentTypeResolver webFluxContentTypeResolver) {
124+
@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
124125
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
125126
mapping.setOrder(0);
126-
mapping.setContentTypeResolver(webFluxContentTypeResolver);
127+
mapping.setContentTypeResolver(contentTypeResolver);
127128
mapping.setCorsConfigurations(getCorsConfigurations());
128129

129130
PathMatchConfigurer configurer = getPathMatchConfigurer();
@@ -265,14 +266,14 @@ protected void addResourceHandlers(ResourceHandlerRegistry registry) {
265266

266267
@Bean
267268
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
268-
ReactiveAdapterRegistry webFluxAdapterRegistry,
269+
@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
269270
ServerCodecConfigurer serverCodecConfigurer,
270-
FormattingConversionService webFluxConversionService,
271-
Validator webfluxValidator) {
271+
@Qualifier("webFluxConversionService") FormattingConversionService conversionService,
272+
@Qualifier("webFluxValidator") Validator validator) {
272273
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
273274
adapter.setMessageReaders(serverCodecConfigurer.getReaders());
274-
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(webFluxConversionService, webfluxValidator));
275-
adapter.setReactiveAdapterRegistry(webFluxAdapterRegistry);
275+
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
276+
adapter.setReactiveAdapterRegistry(reactiveAdapterRegistry);
276277

277278
ArgumentResolverConfigurer configurer = new ArgumentResolverConfigurer();
278279
configureArgumentResolvers(configurer);
@@ -426,30 +427,30 @@ public SimpleHandlerAdapter simpleHandlerAdapter() {
426427

427428
@Bean
428429
public ResponseEntityResultHandler responseEntityResultHandler(
429-
ReactiveAdapterRegistry webFluxAdapterRegistry,
430+
@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
430431
ServerCodecConfigurer serverCodecConfigurer,
431-
RequestedContentTypeResolver webFluxContentTypeResolver) {
432+
@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
432433
return new ResponseEntityResultHandler(serverCodecConfigurer.getWriters(),
433-
webFluxContentTypeResolver, webFluxAdapterRegistry);
434+
contentTypeResolver, reactiveAdapterRegistry);
434435
}
435436

436437
@Bean
437438
public ResponseBodyResultHandler responseBodyResultHandler(
438-
ReactiveAdapterRegistry webFluxAdapterRegistry,
439+
@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
439440
ServerCodecConfigurer serverCodecConfigurer,
440-
RequestedContentTypeResolver webFluxContentTypeResolver) {
441+
@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
441442
return new ResponseBodyResultHandler(serverCodecConfigurer.getWriters(),
442-
webFluxContentTypeResolver, webFluxAdapterRegistry);
443+
contentTypeResolver, reactiveAdapterRegistry);
443444
}
444445

445446
@Bean
446447
public ViewResolutionResultHandler viewResolutionResultHandler(
447-
ReactiveAdapterRegistry webFluxAdapterRegistry,
448-
RequestedContentTypeResolver webFluxContentTypeResolver) {
448+
@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
449+
@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
449450
ViewResolverRegistry registry = getViewResolverRegistry();
450451
List<ViewResolver> resolvers = registry.getViewResolvers();
451452
ViewResolutionResultHandler handler = new ViewResolutionResultHandler(
452-
resolvers, webFluxContentTypeResolver, webFluxAdapterRegistry);
453+
resolvers, contentTypeResolver, reactiveAdapterRegistry);
453454
handler.setDefaultViews(registry.getDefaultViews());
454455
handler.setOrder(registry.getOrder());
455456
return handler;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Copyright 2002-2019 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.web.reactive.config;
18+
19+
import java.util.function.Consumer;
20+
21+
import org.junit.jupiter.api.AfterEach;
22+
import org.junit.jupiter.api.Test;
23+
24+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
25+
import org.springframework.core.ReactiveAdapterRegistry;
26+
import org.springframework.format.support.FormattingConversionService;
27+
import org.springframework.validation.Validator;
28+
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
29+
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
30+
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
31+
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
32+
import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler;
33+
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.mockito.Mockito.mock;
37+
38+
/**
39+
* Integration tests for {@link DelegatingWebFluxConfiguration}.
40+
*
41+
* @author Stephane Nicoll
42+
*/
43+
public class DelegatingWebFluxConfigurationIntegrationTests {
44+
45+
private AnnotationConfigApplicationContext context;
46+
47+
@AfterEach
48+
public void closeContext() {
49+
if (this.context != null) {
50+
this.context.close();
51+
}
52+
}
53+
54+
@Test
55+
void requestMappingHandlerMappingUsesWebFluxInfrastructureByDefault() {
56+
load(context -> { });
57+
RequestMappingHandlerMapping handlerMapping = this.context.getBean(RequestMappingHandlerMapping.class);
58+
assertThat(handlerMapping.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver"));
59+
}
60+
61+
@Test
62+
void requestMappingHandlerMappingWithPrimaryUsesQualifiedRequestedContentTypeResolver() {
63+
load(registerPrimaryBean("testContentTypeResolver", RequestedContentTypeResolver.class));
64+
RequestMappingHandlerMapping handlerMapping = this.context.getBean(RequestMappingHandlerMapping.class);
65+
assertThat(handlerMapping.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver"));
66+
assertThat(this.context.getBeansOfType(RequestedContentTypeResolver.class)).containsOnlyKeys(
67+
"webFluxContentTypeResolver", "testContentTypeResolver");
68+
}
69+
70+
@Test
71+
void requestMappingHandlerAdapterUsesWebFluxInfrastructureByDefault() {
72+
load(context -> { });
73+
RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class);
74+
assertThat(mappingHandlerAdapter.getReactiveAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry"));
75+
assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue("conversionService",
76+
this.context.getBean("webFluxConversionService"));
77+
assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue("validator",
78+
this.context.getBean("webFluxValidator"));
79+
}
80+
81+
@Test
82+
void requestMappingHandlerAdapterWithPrimaryUsesQualifiedReactiveAdapterRegistry() {
83+
load(registerPrimaryBean("testReactiveAdapterRegistry", ReactiveAdapterRegistry.class));
84+
RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class);
85+
assertThat(mappingHandlerAdapter.getReactiveAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry"));
86+
assertThat(this.context.getBeansOfType(ReactiveAdapterRegistry.class)).containsOnlyKeys(
87+
"webFluxAdapterRegistry", "testReactiveAdapterRegistry");
88+
}
89+
90+
@Test
91+
void requestMappingHandlerAdapterWithPrimaryUsesQualifiedConversionService() {
92+
load(registerPrimaryBean("testConversionService", FormattingConversionService.class));
93+
RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class);
94+
assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue("conversionService",
95+
this.context.getBean("webFluxConversionService"));
96+
assertThat(this.context.getBeansOfType(FormattingConversionService.class)).containsOnlyKeys(
97+
"webFluxConversionService", "testConversionService");
98+
}
99+
100+
@Test
101+
void requestMappingHandlerAdapterWithPrimaryUsesQualifiedValidator() {
102+
load(registerPrimaryBean("testValidator", Validator.class));
103+
RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class);
104+
assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue("validator",
105+
this.context.getBean("webFluxValidator"));
106+
assertThat(this.context.getBeansOfType(Validator.class)).containsOnlyKeys(
107+
"webFluxValidator", "testValidator");
108+
}
109+
110+
@Test
111+
void responseEntityResultHandlerUsesWebFluxInfrastructureByDefault() {
112+
load(context -> { });
113+
ResponseEntityResultHandler responseEntityResultHandler = this.context.getBean(ResponseEntityResultHandler.class);
114+
assertThat(responseEntityResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry"));
115+
assertThat(responseEntityResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver"));
116+
}
117+
118+
@Test
119+
void responseEntityResultHandlerWithPrimaryUsesQualifiedReactiveAdapterRegistry() {
120+
load(registerPrimaryBean("testReactiveAdapterRegistry", ReactiveAdapterRegistry.class));
121+
ResponseEntityResultHandler responseEntityResultHandler = this.context.getBean(ResponseEntityResultHandler.class);
122+
assertThat(responseEntityResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry"));
123+
assertThat(this.context.getBeansOfType(ReactiveAdapterRegistry.class)).containsOnlyKeys(
124+
"webFluxAdapterRegistry", "testReactiveAdapterRegistry");
125+
}
126+
127+
@Test
128+
void responseEntityResultHandlerWithPrimaryUsesQualifiedRequestedContentTypeResolver() {
129+
load(registerPrimaryBean("testContentTypeResolver", RequestedContentTypeResolver.class));
130+
ResponseEntityResultHandler responseEntityResultHandler = this.context.getBean(ResponseEntityResultHandler.class);
131+
assertThat(responseEntityResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver"));
132+
assertThat(this.context.getBeansOfType(RequestedContentTypeResolver.class)).containsOnlyKeys(
133+
"webFluxContentTypeResolver", "testContentTypeResolver");
134+
}
135+
136+
@Test
137+
void responseBodyResultHandlerUsesWebFluxInfrastructureByDefault() {
138+
load(context -> { });
139+
ResponseBodyResultHandler responseBodyResultHandler = this.context.getBean(ResponseBodyResultHandler.class);
140+
assertThat(responseBodyResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry"));
141+
assertThat(responseBodyResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver"));
142+
}
143+
144+
@Test
145+
void responseBodyResultHandlerWithPrimaryUsesQualifiedReactiveAdapterRegistry() {
146+
load(registerPrimaryBean("testReactiveAdapterRegistry", ReactiveAdapterRegistry.class));
147+
ResponseBodyResultHandler responseBodyResultHandler = this.context.getBean(ResponseBodyResultHandler.class);
148+
assertThat(responseBodyResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry"));
149+
assertThat(this.context.getBeansOfType(ReactiveAdapterRegistry.class)).containsOnlyKeys(
150+
"webFluxAdapterRegistry", "testReactiveAdapterRegistry");
151+
}
152+
153+
@Test
154+
void responseBodyResultHandlerWithPrimaryUsesQualifiedRequestedContentTypeResolver() {
155+
load(registerPrimaryBean("testContentTypeResolver", RequestedContentTypeResolver.class));
156+
ResponseBodyResultHandler responseBodyResultHandler = this.context.getBean(ResponseBodyResultHandler.class);
157+
assertThat(responseBodyResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver"));
158+
assertThat(this.context.getBeansOfType(RequestedContentTypeResolver.class)).containsOnlyKeys(
159+
"webFluxContentTypeResolver", "testContentTypeResolver");
160+
}
161+
162+
@Test
163+
void viewResolutionResultHandlerUsesWebFluxInfrastructureByDefault() {
164+
load(context -> { });
165+
ViewResolutionResultHandler viewResolutionResultHandler = this.context.getBean(ViewResolutionResultHandler.class);
166+
assertThat(viewResolutionResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry"));
167+
assertThat(viewResolutionResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver"));
168+
}
169+
170+
@Test
171+
void viewResolutionResultHandlerWithPrimaryUsesQualifiedReactiveAdapterRegistry() {
172+
load(registerPrimaryBean("testReactiveAdapterRegistry", ReactiveAdapterRegistry.class));
173+
ViewResolutionResultHandler viewResolutionResultHandler = this.context.getBean(ViewResolutionResultHandler.class);
174+
assertThat(viewResolutionResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry"));
175+
assertThat(this.context.getBeansOfType(ReactiveAdapterRegistry.class)).containsOnlyKeys(
176+
"webFluxAdapterRegistry", "testReactiveAdapterRegistry");
177+
}
178+
179+
@Test
180+
void viewResolutionResultHandlerWithPrimaryUsesQualifiedRequestedContentTypeResolver() {
181+
load(registerPrimaryBean("testContentTypeResolver", RequestedContentTypeResolver.class));
182+
ViewResolutionResultHandler viewResolutionResultHandler = this.context.getBean(ViewResolutionResultHandler.class);
183+
assertThat(viewResolutionResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver"));
184+
assertThat(this.context.getBeansOfType(RequestedContentTypeResolver.class)).containsOnlyKeys(
185+
"webFluxContentTypeResolver", "testContentTypeResolver");
186+
}
187+
188+
private <T> Consumer<AnnotationConfigApplicationContext> registerPrimaryBean(String beanName, Class<T> type) {
189+
return context -> context.registerBean(beanName, type, () -> mock(type), definition -> definition.setPrimary(true));
190+
}
191+
192+
private void load(Consumer<AnnotationConfigApplicationContext> context) {
193+
AnnotationConfigApplicationContext testContext = new AnnotationConfigApplicationContext();
194+
context.accept(testContext);
195+
testContext.registerBean(DelegatingWebFluxConfiguration.class);
196+
testContext.refresh();
197+
this.context = testContext;
198+
}
199+
}

0 commit comments

Comments
 (0)