From 6b7dfce5c6e511ea4eca43935d12d2270cf6b67d Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 7 Jun 2017 17:17:57 +0100 Subject: [PATCH 1/2] Formatting --- .../web/WebMvcAutoConfigurationTests.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java index f76a7681aa..2ab36dccc3 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java @@ -673,7 +673,8 @@ public class WebMvcAutoConfigurationTests { .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); - assertThat(springValidatorBeans).containsExactly("defaultValidator", "mvcValidator"); + assertThat(springValidatorBeans).containsExactly("defaultValidator", + "mvcValidator"); Validator validator = this.context.getBean("mvcValidator", Validator.class); assertThat(validator).isInstanceOf(WebMvcValidator.class); Object defaultValidator = this.context.getBean("defaultValidator"); @@ -696,7 +697,8 @@ public class WebMvcAutoConfigurationTests { @Test public void validatorWithConfigurerDoesNotExposeJsr303() { - load(MvcJsr303Validator.class, new Class[] { ValidationAutoConfiguration.class }); + load(MvcJsr303Validator.class, + new Class[] { ValidationAutoConfiguration.class }); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) .isEmpty(); @@ -715,7 +717,8 @@ public class WebMvcAutoConfigurationTests { assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) .hasSize(1); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); - assertThat(springValidatorBeans).containsExactly("defaultValidator", "mvcValidator"); + assertThat(springValidatorBeans).containsExactly("defaultValidator", + "mvcValidator"); assertThat(this.context.getBean("mvcValidator")) .isSameAs(this.context.getBean(MvcValidator.class).validator); // Primary Spring validator is the auto-configured one as the MVC one has been @@ -731,13 +734,12 @@ public class WebMvcAutoConfigurationTests { .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); - assertThat(springValidatorBeans).containsExactly( - "customSpringValidator", "defaultValidator", "mvcValidator"); + assertThat(springValidatorBeans).containsExactly("customSpringValidator", + "defaultValidator", "mvcValidator"); Validator validator = this.context.getBean("mvcValidator", Validator.class); assertThat(validator).isInstanceOf(WebMvcValidator.class); Object defaultValidator = this.context.getBean("defaultValidator"); - assertThat(((WebMvcValidator) validator).getTarget()) - .isSameAs(defaultValidator); + assertThat(((WebMvcValidator) validator).getTarget()).isSameAs(defaultValidator); // Primary Spring validator is the one used by MVC behind the scenes assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator); } @@ -753,8 +755,7 @@ public class WebMvcAutoConfigurationTests { assertThat(springValidatorBeans).containsExactly("mvcValidator"); Validator validator = this.context.getBean(Validator.class); assertThat(validator).isInstanceOf(WebMvcValidator.class); - SpringValidatorAdapter target = ((WebMvcValidator) validator) - .getTarget(); + SpringValidatorAdapter target = ((WebMvcValidator) validator).getTarget(); assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator")) .isSameAs(this.context.getBean("customJsr303Validator")); } From e5906a6b64faa105a918fdf3f0396e90b3a08500 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 7 Jun 2017 16:58:32 +0100 Subject: [PATCH 2/2] Allow HttpMsgConverter to depend on ConvService without creating a cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In an MVC web application, DelegatingWebMvcConfiguration provides the ConversionService while also consuming WebMvcConfigurerAdapters that, among other things, can configure HTTP message converters. Boot's WebMvcConfigurerAdapter, WebMvcAutoConfigurationAdapter, consumes the HttpMessageConverters bean and uses it to configure Spring MVC's HTTP message converters. This can create a bean dependency cycle if an HTTP message converter bean depends, directly or indirectly on the ConversionService. An example of the cycle is: ┌─────┐ | jsonComponentConversionServiceCycle.ThingDeserializer defined in … ↑ ↓ | org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration ↑ ↓ | org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter ↑ ↓ | org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration ↑ ↓ | mappingJackson2HttpMessageConverter defined in class path resource [org/springframework/boot/autoconfigure/web/JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration.class] ↑ ↓ | jacksonObjectMapper defined in class path resource [org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration$JacksonObjectMapperConfiguration.class] └─────┘ This commit breaks the cycle by making WebMvcAutoConfigurationAdapter consume HttpMessageConverters lazily. This allows the adapter to be created without triggered instantiation of every HTTP message converter bean and all their dependencies. This allows it to be injected into DelegatingWebMvcConfiguration without triggering an attempt to retrieve the ConversionService. Closes gh-9409 --- .../web/WebMvcAutoConfiguration.java | 3 ++- .../web/WebMvcAutoConfigurationTests.java | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java index 1428f80d31..cdd704cde6 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java @@ -52,6 +52,7 @@ import org.springframework.boot.web.filter.OrderedRequestContextFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; import org.springframework.core.convert.converter.Converter; @@ -170,7 +171,7 @@ public class WebMvcAutoConfiguration { public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, - HttpMessageConverters messageConverters, + @Lazy HttpMessageConverters messageConverters, ObjectProvider resourceHandlerRegistrationCustomizerProvider) { this.resourceProperties = resourceProperties; this.mvcProperties = mvcProperties; diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java index 2ab36dccc3..aa89133a82 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java @@ -51,11 +51,13 @@ import org.springframework.boot.web.filter.OrderedHttpPutFormContentFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.core.convert.ConversionService; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; @@ -760,6 +762,11 @@ public class WebMvcAutoConfigurationTests { .isSameAs(this.context.getBean("customJsr303Validator")); } + @Test + public void httpMessageConverterThatUsesConversionServiceDoesNotCreateACycle() { + load(CustomHttpMessageConverter.class); + } + private void load(Class config, String... environment) { load(config, null, environment); } @@ -990,4 +997,15 @@ public class WebMvcAutoConfigurationTests { } + @Configuration + static class CustomHttpMessageConverter { + + @Bean + public HttpMessageConverter customHttpMessageConverter( + ConversionService conversionService) { + return mock(HttpMessageConverter.class); + } + + } + }