diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java index ab99a8d5d1c5..f2f40e8ea506 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java @@ -24,6 +24,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; @@ -120,10 +121,10 @@ public WebExceptionHandler responseStatusExceptionHandler() { @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping( - RequestedContentTypeResolver webFluxContentTypeResolver) { + @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) { RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping(); mapping.setOrder(0); - mapping.setContentTypeResolver(webFluxContentTypeResolver); + mapping.setContentTypeResolver(contentTypeResolver); mapping.setCorsConfigurations(getCorsConfigurations()); PathMatchConfigurer configurer = getPathMatchConfigurer(); @@ -265,14 +266,14 @@ protected void addResourceHandlers(ResourceHandlerRegistry registry) { @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter( - ReactiveAdapterRegistry webFluxAdapterRegistry, + @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry, ServerCodecConfigurer serverCodecConfigurer, - FormattingConversionService webFluxConversionService, - Validator webfluxValidator) { + @Qualifier("webFluxConversionService") FormattingConversionService conversionService, + @Qualifier("webFluxValidator") Validator validator) { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); adapter.setMessageReaders(serverCodecConfigurer.getReaders()); - adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(webFluxConversionService, webfluxValidator)); - adapter.setReactiveAdapterRegistry(webFluxAdapterRegistry); + adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator)); + adapter.setReactiveAdapterRegistry(reactiveAdapterRegistry); ArgumentResolverConfigurer configurer = new ArgumentResolverConfigurer(); configureArgumentResolvers(configurer); @@ -426,30 +427,30 @@ public SimpleHandlerAdapter simpleHandlerAdapter() { @Bean public ResponseEntityResultHandler responseEntityResultHandler( - ReactiveAdapterRegistry webFluxAdapterRegistry, + @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry, ServerCodecConfigurer serverCodecConfigurer, - RequestedContentTypeResolver webFluxContentTypeResolver) { + @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) { return new ResponseEntityResultHandler(serverCodecConfigurer.getWriters(), - webFluxContentTypeResolver, webFluxAdapterRegistry); + contentTypeResolver, reactiveAdapterRegistry); } @Bean public ResponseBodyResultHandler responseBodyResultHandler( - ReactiveAdapterRegistry webFluxAdapterRegistry, + @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry, ServerCodecConfigurer serverCodecConfigurer, - RequestedContentTypeResolver webFluxContentTypeResolver) { + @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) { return new ResponseBodyResultHandler(serverCodecConfigurer.getWriters(), - webFluxContentTypeResolver, webFluxAdapterRegistry); + contentTypeResolver, reactiveAdapterRegistry); } @Bean public ViewResolutionResultHandler viewResolutionResultHandler( - ReactiveAdapterRegistry webFluxAdapterRegistry, - RequestedContentTypeResolver webFluxContentTypeResolver) { + @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry, + @Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) { ViewResolverRegistry registry = getViewResolverRegistry(); List resolvers = registry.getViewResolvers(); ViewResolutionResultHandler handler = new ViewResolutionResultHandler( - resolvers, webFluxContentTypeResolver, webFluxAdapterRegistry); + resolvers, contentTypeResolver, reactiveAdapterRegistry); handler.setDefaultViews(registry.getDefaultViews()); handler.setOrder(registry.getOrder()); return handler; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationIntegrationTests.java new file mode 100644 index 000000000000..35df96e88bba --- /dev/null +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationIntegrationTests.java @@ -0,0 +1,199 @@ +/* + * Copyright 2002-2019 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.web.reactive.config; + +import java.util.function.Consumer; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.ReactiveAdapterRegistry; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.validation.Validator; +import org.springframework.web.reactive.accept.RequestedContentTypeResolver; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler; +import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler; +import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Integration tests for {@link DelegatingWebFluxConfiguration}. + * + * @author Stephane Nicoll + */ +public class DelegatingWebFluxConfigurationIntegrationTests { + + private AnnotationConfigApplicationContext context; + + @AfterEach + public void closeContext() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + void requestMappingHandlerMappingUsesWebFluxInfrastructureByDefault() { + load(context -> { }); + RequestMappingHandlerMapping handlerMapping = this.context.getBean(RequestMappingHandlerMapping.class); + assertThat(handlerMapping.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver")); + } + + @Test + void requestMappingHandlerMappingWithPrimaryUsesQualifiedRequestedContentTypeResolver() { + load(registerPrimaryBean("testContentTypeResolver", RequestedContentTypeResolver.class)); + RequestMappingHandlerMapping handlerMapping = this.context.getBean(RequestMappingHandlerMapping.class); + assertThat(handlerMapping.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver")); + assertThat(this.context.getBeansOfType(RequestedContentTypeResolver.class)).containsOnlyKeys( + "webFluxContentTypeResolver", "testContentTypeResolver"); + } + + @Test + void requestMappingHandlerAdapterUsesWebFluxInfrastructureByDefault() { + load(context -> { }); + RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class); + assertThat(mappingHandlerAdapter.getReactiveAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry")); + assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue("conversionService", + this.context.getBean("webFluxConversionService")); + assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue("validator", + this.context.getBean("webFluxValidator")); + } + + @Test + void requestMappingHandlerAdapterWithPrimaryUsesQualifiedReactiveAdapterRegistry() { + load(registerPrimaryBean("testReactiveAdapterRegistry", ReactiveAdapterRegistry.class)); + RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class); + assertThat(mappingHandlerAdapter.getReactiveAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry")); + assertThat(this.context.getBeansOfType(ReactiveAdapterRegistry.class)).containsOnlyKeys( + "webFluxAdapterRegistry", "testReactiveAdapterRegistry"); + } + + @Test + void requestMappingHandlerAdapterWithPrimaryUsesQualifiedConversionService() { + load(registerPrimaryBean("testConversionService", FormattingConversionService.class)); + RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class); + assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue("conversionService", + this.context.getBean("webFluxConversionService")); + assertThat(this.context.getBeansOfType(FormattingConversionService.class)).containsOnlyKeys( + "webFluxConversionService", "testConversionService"); + } + + @Test + void requestMappingHandlerAdapterWithPrimaryUsesQualifiedValidator() { + load(registerPrimaryBean("testValidator", Validator.class)); + RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class); + assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue("validator", + this.context.getBean("webFluxValidator")); + assertThat(this.context.getBeansOfType(Validator.class)).containsOnlyKeys( + "webFluxValidator", "testValidator"); + } + + @Test + void responseEntityResultHandlerUsesWebFluxInfrastructureByDefault() { + load(context -> { }); + ResponseEntityResultHandler responseEntityResultHandler = this.context.getBean(ResponseEntityResultHandler.class); + assertThat(responseEntityResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry")); + assertThat(responseEntityResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver")); + } + + @Test + void responseEntityResultHandlerWithPrimaryUsesQualifiedReactiveAdapterRegistry() { + load(registerPrimaryBean("testReactiveAdapterRegistry", ReactiveAdapterRegistry.class)); + ResponseEntityResultHandler responseEntityResultHandler = this.context.getBean(ResponseEntityResultHandler.class); + assertThat(responseEntityResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry")); + assertThat(this.context.getBeansOfType(ReactiveAdapterRegistry.class)).containsOnlyKeys( + "webFluxAdapterRegistry", "testReactiveAdapterRegistry"); + } + + @Test + void responseEntityResultHandlerWithPrimaryUsesQualifiedRequestedContentTypeResolver() { + load(registerPrimaryBean("testContentTypeResolver", RequestedContentTypeResolver.class)); + ResponseEntityResultHandler responseEntityResultHandler = this.context.getBean(ResponseEntityResultHandler.class); + assertThat(responseEntityResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver")); + assertThat(this.context.getBeansOfType(RequestedContentTypeResolver.class)).containsOnlyKeys( + "webFluxContentTypeResolver", "testContentTypeResolver"); + } + + @Test + void responseBodyResultHandlerUsesWebFluxInfrastructureByDefault() { + load(context -> { }); + ResponseBodyResultHandler responseBodyResultHandler = this.context.getBean(ResponseBodyResultHandler.class); + assertThat(responseBodyResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry")); + assertThat(responseBodyResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver")); + } + + @Test + void responseBodyResultHandlerWithPrimaryUsesQualifiedReactiveAdapterRegistry() { + load(registerPrimaryBean("testReactiveAdapterRegistry", ReactiveAdapterRegistry.class)); + ResponseBodyResultHandler responseBodyResultHandler = this.context.getBean(ResponseBodyResultHandler.class); + assertThat(responseBodyResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry")); + assertThat(this.context.getBeansOfType(ReactiveAdapterRegistry.class)).containsOnlyKeys( + "webFluxAdapterRegistry", "testReactiveAdapterRegistry"); + } + + @Test + void responseBodyResultHandlerWithPrimaryUsesQualifiedRequestedContentTypeResolver() { + load(registerPrimaryBean("testContentTypeResolver", RequestedContentTypeResolver.class)); + ResponseBodyResultHandler responseBodyResultHandler = this.context.getBean(ResponseBodyResultHandler.class); + assertThat(responseBodyResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver")); + assertThat(this.context.getBeansOfType(RequestedContentTypeResolver.class)).containsOnlyKeys( + "webFluxContentTypeResolver", "testContentTypeResolver"); + } + + @Test + void viewResolutionResultHandlerUsesWebFluxInfrastructureByDefault() { + load(context -> { }); + ViewResolutionResultHandler viewResolutionResultHandler = this.context.getBean(ViewResolutionResultHandler.class); + assertThat(viewResolutionResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry")); + assertThat(viewResolutionResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver")); + } + + @Test + void viewResolutionResultHandlerWithPrimaryUsesQualifiedReactiveAdapterRegistry() { + load(registerPrimaryBean("testReactiveAdapterRegistry", ReactiveAdapterRegistry.class)); + ViewResolutionResultHandler viewResolutionResultHandler = this.context.getBean(ViewResolutionResultHandler.class); + assertThat(viewResolutionResultHandler.getAdapterRegistry()).isSameAs(this.context.getBean("webFluxAdapterRegistry")); + assertThat(this.context.getBeansOfType(ReactiveAdapterRegistry.class)).containsOnlyKeys( + "webFluxAdapterRegistry", "testReactiveAdapterRegistry"); + } + + @Test + void viewResolutionResultHandlerWithPrimaryUsesQualifiedRequestedContentTypeResolver() { + load(registerPrimaryBean("testContentTypeResolver", RequestedContentTypeResolver.class)); + ViewResolutionResultHandler viewResolutionResultHandler = this.context.getBean(ViewResolutionResultHandler.class); + assertThat(viewResolutionResultHandler.getContentTypeResolver()).isSameAs(this.context.getBean("webFluxContentTypeResolver")); + assertThat(this.context.getBeansOfType(RequestedContentTypeResolver.class)).containsOnlyKeys( + "webFluxContentTypeResolver", "testContentTypeResolver"); + } + + private Consumer registerPrimaryBean(String beanName, Class type) { + return context -> context.registerBean(beanName, type, () -> mock(type), definition -> definition.setPrimary(true)); + } + + private void load(Consumer context) { + AnnotationConfigApplicationContext testContext = new AnnotationConfigApplicationContext(); + context.accept(testContext); + testContext.registerBean(DelegatingWebFluxConfiguration.class); + testContext.refresh(); + this.context = testContext; + } +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index e59868bfba25..d8f93bf4e545 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -29,6 +29,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; @@ -275,13 +276,14 @@ public final ServletContext getServletContext() { */ @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping( - ContentNegotiationManager mvcContentNegotiationManager, - FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { + @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, + @Qualifier("mvcConversionService") FormattingConversionService conversionService, + @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) { RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping(); mapping.setOrder(0); - mapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); - mapping.setContentNegotiationManager(mvcContentNegotiationManager); + mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider)); + mapping.setContentNegotiationManager(contentNegotiationManager); mapping.setCorsConfigurations(getCorsConfigurations()); PathMatchConfigurer configurer = getPathMatchConfigurer(); @@ -447,10 +449,11 @@ protected void configureContentNegotiation(ContentNegotiationConfigurer configur */ @Bean @Nullable - public HandlerMapping viewControllerHandlerMapping(PathMatcher mvcPathMatcher, - UrlPathHelper mvcUrlPathHelper, - FormattingConversionService mvcConversionService, - ResourceUrlProvider mvcResourceUrlProvider) { + public HandlerMapping viewControllerHandlerMapping( + @Qualifier("mvcPathMatcher") PathMatcher pathMatcher, + @Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper, + @Qualifier("mvcConversionService") FormattingConversionService conversionService, + @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) { ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext); addViewControllers(registry); @@ -458,9 +461,9 @@ public HandlerMapping viewControllerHandlerMapping(PathMatcher mvcPathMatcher, if (handlerMapping == null) { return null; } - handlerMapping.setPathMatcher(mvcPathMatcher); - handlerMapping.setUrlPathHelper(mvcUrlPathHelper); - handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); + handlerMapping.setPathMatcher(pathMatcher); + handlerMapping.setUrlPathHelper(urlPathHelper); + handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider)); handlerMapping.setCorsConfigurations(getCorsConfigurations()); return handlerMapping; } @@ -478,11 +481,12 @@ protected void addViewControllers(ViewControllerRegistry registry) { */ @Bean public BeanNameUrlHandlerMapping beanNameHandlerMapping( - FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { + @Qualifier("mvcConversionService") FormattingConversionService conversionService, + @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) { BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping(); mapping.setOrder(2); - mapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); + mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider)); mapping.setCorsConfigurations(getCorsConfigurations()); return mapping; } @@ -500,11 +504,12 @@ public BeanNameUrlHandlerMapping beanNameHandlerMapping( */ @Bean public RouterFunctionMapping routerFunctionMapping( - FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { + @Qualifier("mvcConversionService") FormattingConversionService conversionService, + @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) { RouterFunctionMapping mapping = new RouterFunctionMapping(); mapping.setOrder(3); - mapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); + mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider)); mapping.setCorsConfigurations(getCorsConfigurations()); mapping.setMessageConverters(getMessageConverters()); return mapping; @@ -517,24 +522,27 @@ public RouterFunctionMapping routerFunctionMapping( */ @Bean @Nullable - public HandlerMapping resourceHandlerMapping(UrlPathHelper mvcUrlPathHelper, - PathMatcher mvcPathMatcher, ContentNegotiationManager mvcContentNegotiationManager, - FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { + public HandlerMapping resourceHandlerMapping( + @Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper, + @Qualifier("mvcPathMatcher") PathMatcher pathMatcher, + @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, + @Qualifier("mvcConversionService") FormattingConversionService conversionService, + @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) { Assert.state(this.applicationContext != null, "No ApplicationContext set"); Assert.state(this.servletContext != null, "No ServletContext set"); ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, - this.servletContext, mvcContentNegotiationManager, mvcUrlPathHelper); + this.servletContext, contentNegotiationManager, urlPathHelper); addResourceHandlers(registry); AbstractHandlerMapping handlerMapping = registry.getHandlerMapping(); if (handlerMapping == null) { return null; } - handlerMapping.setPathMatcher(mvcPathMatcher); - handlerMapping.setUrlPathHelper(mvcUrlPathHelper); - handlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); + handlerMapping.setPathMatcher(pathMatcher); + handlerMapping.setUrlPathHelper(urlPathHelper); + handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider)); handlerMapping.setCorsConfigurations(getCorsConfigurations()); return handlerMapping; } @@ -597,13 +605,14 @@ protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer c */ @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter( - ContentNegotiationManager mvcContentNegotiationManager, - FormattingConversionService mvcConversionService, Validator mvcValidator) { + @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, + @Qualifier("mvcConversionService") FormattingConversionService conversionService, + @Qualifier("mvcValidator") Validator validator) { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); - adapter.setContentNegotiationManager(mvcContentNegotiationManager); + adapter.setContentNegotiationManager(contentNegotiationManager); adapter.setMessageConverters(getMessageConverters()); - adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator)); + adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator)); adapter.setCustomArgumentResolvers(getArgumentResolvers()); adapter.setCustomReturnValueHandlers(getReturnValueHandlers()); @@ -898,10 +907,10 @@ else if (jsonbPresent) { */ @Bean public CompositeUriComponentsContributor mvcUriComponentsContributor( - FormattingConversionService mvcConversionService, - RequestMappingHandlerAdapter requestMappingHandlerAdapter) { + @Qualifier("mvcConversionService") FormattingConversionService conversionService, + @Qualifier("requestMappingHandlerAdapter") RequestMappingHandlerAdapter requestMappingHandlerAdapter) { return new CompositeUriComponentsContributor( - requestMappingHandlerAdapter.getArgumentResolvers(), mvcConversionService); + requestMappingHandlerAdapter.getArgumentResolvers(), conversionService); } /** @@ -932,11 +941,11 @@ public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() { */ @Bean public HandlerExceptionResolver handlerExceptionResolver( - ContentNegotiationManager mvcContentNegotiationManager) { + @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) { List exceptionResolvers = new ArrayList<>(); configureHandlerExceptionResolvers(exceptionResolvers); if (exceptionResolvers.isEmpty()) { - addDefaultHandlerExceptionResolvers(exceptionResolvers, mvcContentNegotiationManager); + addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager); } extendHandlerExceptionResolvers(exceptionResolvers); HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); @@ -1026,9 +1035,10 @@ protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResol * @since 4.1 */ @Bean - public ViewResolver mvcViewResolver(ContentNegotiationManager mvcContentNegotiationManager) { + public ViewResolver mvcViewResolver( + @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) { ViewResolverRegistry registry = - new ViewResolverRegistry(mvcContentNegotiationManager, this.applicationContext); + new ViewResolverRegistry(contentNegotiationManager, this.applicationContext); configureViewResolvers(registry); if (registry.getViewResolvers().isEmpty() && this.applicationContext != null) { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationIntegrationTests.java new file mode 100644 index 000000000000..f1ba8110e438 --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationIntegrationTests.java @@ -0,0 +1,201 @@ +/* + * Copyright 2002-2019 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.web.servlet.config.annotation; + +import java.util.function.Consumer; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigUtils; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.ConversionService; +import org.springframework.mock.web.test.MockServletContext; +import org.springframework.util.PathMatcher; +import org.springframework.validation.Validator; +import org.springframework.web.accept.ContentNegotiationManager; +import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.servlet.handler.AbstractHandlerMapping; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.util.UrlPathHelper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Integration tests for {@link DelegatingWebMvcConfiguration}. + * + * @author Stephane Nicoll + */ +public class DelegatingWebMvcConfigurationIntegrationTests { + + private ConfigurableApplicationContext context; + + @AfterEach + public void closeContext() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + void requestMappingHandlerMappingUsesMvcInfrastructureByDefault() { + load(context -> { }); + RequestMappingHandlerMapping handlerMapping = this.context.getBean(RequestMappingHandlerMapping.class); + assertThat(handlerMapping.getContentNegotiationManager()).isSameAs(this.context.getBean("mvcContentNegotiationManager")); + } + + @Test + void requestMappingHandlerMappingWithPrimaryUsesQualifiedContentNegotiationManager() { + load(registerPrimaryBean("testContentNegotiationManager", ContentNegotiationManager.class)); + RequestMappingHandlerMapping handlerMapping = this.context.getBean(RequestMappingHandlerMapping.class); + assertThat(handlerMapping.getContentNegotiationManager()).isSameAs(this.context.getBean("mvcContentNegotiationManager")); + assertThat(this.context.getBeansOfType(ContentNegotiationManager.class)).containsOnlyKeys( + "mvcContentNegotiationManager", "testContentNegotiationManager"); + } + + @Test + void viewControllerHandlerMappingUsesMvcInfrastructureByDefault() { + load(context -> context.registerBean(ViewControllerConfiguration.class)); + AbstractHandlerMapping handlerMapping = this.context.getBean("viewControllerHandlerMapping", AbstractHandlerMapping.class); + assertThat(handlerMapping.getPathMatcher()).isSameAs(this.context.getBean("mvcPathMatcher")); + assertThat(handlerMapping.getUrlPathHelper()).isSameAs(this.context.getBean("mvcUrlPathHelper")); + } + + @Test + void viewControllerHandlerMappingWithPrimaryUsesQualifiedPathMatcher() { + load(registerPrimaryBean("testPathMatcher", PathMatcher.class) + .andThen(context -> context.registerBean(ViewControllerConfiguration.class))); + AbstractHandlerMapping handlerMapping = this.context.getBean("viewControllerHandlerMapping", AbstractHandlerMapping.class); + assertThat(handlerMapping.getPathMatcher()).isSameAs(this.context.getBean("mvcPathMatcher")); + assertThat(this.context.getBeansOfType(PathMatcher.class)).containsOnlyKeys( + "mvcPathMatcher", "testPathMatcher"); + } + + @Test + void viewControllerHandlerMappingWithPrimaryUsesQualifiedUrlPathHelper() { + load(registerPrimaryBean("testUrlPathHelper", UrlPathHelper.class) + .andThen(context -> context.registerBean(ViewControllerConfiguration.class))); + AbstractHandlerMapping handlerMapping = this.context.getBean("viewControllerHandlerMapping", AbstractHandlerMapping.class); + assertThat(handlerMapping.getUrlPathHelper()).isSameAs(this.context.getBean("mvcUrlPathHelper")); + assertThat(this.context.getBeansOfType(UrlPathHelper.class)).containsOnlyKeys( + "mvcUrlPathHelper", "testUrlPathHelper"); + } + + @Test + void resourceHandlerMappingUsesMvcInfrastructureByDefault() { + load(context -> context.registerBean(ResourceHandlerConfiguration.class)); + AbstractHandlerMapping handlerMapping = this.context.getBean("resourceHandlerMapping", AbstractHandlerMapping.class); + assertThat(handlerMapping.getPathMatcher()).isSameAs(this.context.getBean("mvcPathMatcher")); + assertThat(handlerMapping.getUrlPathHelper()).isSameAs(this.context.getBean("mvcUrlPathHelper")); + } + + @Test + void resourceHandlerMappingWithPrimaryUsesQualifiedPathMatcher() { + load(registerPrimaryBean("testPathMatcher", PathMatcher.class) + .andThen(context -> context.registerBean(ResourceHandlerConfiguration.class))); + AbstractHandlerMapping handlerMapping = this.context.getBean("resourceHandlerMapping", AbstractHandlerMapping.class); + assertThat(handlerMapping.getPathMatcher()).isSameAs(this.context.getBean("mvcPathMatcher")); + assertThat(this.context.getBeansOfType(PathMatcher.class)).containsOnlyKeys( + "mvcPathMatcher", "testPathMatcher"); + } + + @Test + void resourceHandlerMappingWithPrimaryUsesQualifiedUrlPathHelper() { + load(registerPrimaryBean("testUrlPathHelper", UrlPathHelper.class) + .andThen(context -> context.registerBean(ResourceHandlerConfiguration.class))); + AbstractHandlerMapping handlerMapping = this.context.getBean("resourceHandlerMapping", AbstractHandlerMapping.class); + assertThat(handlerMapping.getUrlPathHelper()).isSameAs(this.context.getBean("mvcUrlPathHelper")); + assertThat(this.context.getBeansOfType(UrlPathHelper.class)).containsOnlyKeys( + "mvcUrlPathHelper", "testUrlPathHelper"); + } + + @Test + void requestMappingHandlerAdapterUsesMvcInfrastructureByDefault() { + load(context -> { }); + RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class); + assertThat(mappingHandlerAdapter).hasFieldOrPropertyWithValue( + "contentNegotiationManager", this.context.getBean("mvcContentNegotiationManager")); + assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue( + "conversionService", this.context.getBean("mvcConversionService")); + assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue( + "validator", this.context.getBean("mvcValidator")); + } + + @Test + void requestMappingHandlerAdapterWithPrimaryUsesQualifiedContentNegotiationManager() { + load(registerPrimaryBean("testContentNegotiationManager", ContentNegotiationManager.class)); + RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class); + assertThat(mappingHandlerAdapter).hasFieldOrPropertyWithValue( + "contentNegotiationManager", this.context.getBean("mvcContentNegotiationManager")); + assertThat(this.context.getBeansOfType(ContentNegotiationManager.class)).containsOnlyKeys( + "mvcContentNegotiationManager", "testContentNegotiationManager"); + } + + @Test + void requestMappingHandlerAdapterWithPrimaryUsesQualifiedConversionService() { + load(registerPrimaryBean("testConversionService", ConversionService.class)); + RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class); + assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue( + "conversionService", this.context.getBean("mvcConversionService")); + assertThat(this.context.getBeansOfType(ConversionService.class)).containsOnlyKeys("mvcConversionService", "testConversionService"); + } + + @Test + void requestMappingHandlerAdapterWithPrimaryUsesQualifiedValidator() { + load(registerPrimaryBean("testValidator", Validator.class)); + RequestMappingHandlerAdapter mappingHandlerAdapter = this.context.getBean(RequestMappingHandlerAdapter.class); + assertThat(mappingHandlerAdapter.getWebBindingInitializer()).hasFieldOrPropertyWithValue( + "validator", this.context.getBean("mvcValidator")); + assertThat(this.context.getBeansOfType(Validator.class)).containsOnlyKeys("mvcValidator", "testValidator"); + } + + private Consumer registerPrimaryBean(String beanName, Class type) { + return context -> context.registerBean(beanName, type, () -> mock(type), definition -> definition.setPrimary(true)); + } + + private void load(Consumer context) { + GenericWebApplicationContext webContext = new GenericWebApplicationContext(); + AnnotationConfigUtils.registerAnnotationConfigProcessors(webContext); + webContext.setServletContext(new MockServletContext()); + + context.accept(webContext); + webContext.registerBean(DelegatingWebMvcConfiguration.class); + webContext.refresh(); + this.context = webContext; + } + + @Configuration + static class ViewControllerConfiguration implements WebMvcConfigurer { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/test"); + } + } + + @Configuration + static class ResourceHandlerConfiguration implements WebMvcConfigurer { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**"); + } + } +}