Allow HttpMsgConverter to depend on ConvService without creating a cycle

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
pull/7877/merge
Andy Wilkinson 8 years ago
parent 6b7dfce5c6
commit e5906a6b64

@ -52,6 +52,7 @@ import org.springframework.boot.web.filter.OrderedRequestContextFilter;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
@ -170,7 +171,7 @@ public class WebMvcAutoConfiguration {
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
HttpMessageConverters messageConverters, @Lazy HttpMessageConverters messageConverters,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) { ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
this.resourceProperties = resourceProperties; this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties; this.mvcProperties = mvcProperties;

@ -51,11 +51,13 @@ import org.springframework.boot.web.filter.OrderedHttpPutFormContentFilter;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.format.support.FormattingConversionService; import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@ -760,6 +762,11 @@ public class WebMvcAutoConfigurationTests {
.isSameAs(this.context.getBean("customJsr303Validator")); .isSameAs(this.context.getBean("customJsr303Validator"));
} }
@Test
public void httpMessageConverterThatUsesConversionServiceDoesNotCreateACycle() {
load(CustomHttpMessageConverter.class);
}
private void load(Class<?> config, String... environment) { private void load(Class<?> config, String... environment) {
load(config, null, 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);
}
}
} }

Loading…
Cancel
Save