I'm using spring-boot
with spring-web
and jackson
.
Problem: when a RestTemplate
is initialized automatically by spring, the constructor receives some duplicate MessageConverters
:
org.springframework.http.converter.ByteArrayHttpMessageConverter@6a1b4854,
org.springframework.http.converter.StringHttpMessageConverter@2d5b549b,
org.springframework.http.converter.StringHttpMessageConverter@6a175162,
org.springframework.http.converter.ResourceHttpMessageConverter@7641c4e7,
org.springframework.http.converter.ResourceRegionHttpMessageConverter@650a0b50,
org.springframework.http.converter.xml.SourceHttpMessageConverter@55e3b64d,
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@52f71d2,
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@f3c27e9,
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@7d31fb6c,
org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter@701c413,
org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter@48543f11
You see, there are 3 duplicates:
StringHttpMessageConverter
MappingJackson2HttpMessageConverter
MappingJackson2XmlHttpMessageConverter
As I don't initialize any message converters myself: why does the application context contain duplicate converters at all, that are then added to the resttemplate?
Especially: doesn't this confuse the (de)serializing if some converters occur duplicate (but with different configuration)?
For example: the ObjectMapper
of the first MappingJackson2HttpMessageConverter
contains more registeredModuleTypes
[Jdk8Module, JavaTimeModule, ParamterNamesModule, JsonComponentModule, GeoModule]
than the 2nd one (that only contains: [Jdk8Module, JavaTimeModule]
).
Does that make sense?
It's instantiated via RestTemplateAutoConfiguration.restTemplateBuilder()
, there all the duplicate MessageConverters
are already present.
The culprit is here, at HttpMessageConverters
public HttpMessageConverters(boolean addDefaultConverters,
Collection<HttpMessageConverter<?>> converters) {
List<HttpMessageConverter<?>> combined = getCombinedConverters(converters,
addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
combined = postProcessConverters(combined);
this.converters = Collections.unmodifiableList(combined);
}
Specifically, this line (formatted)
List<HttpMessageConverter<?>> combined =
getCombinedConverters(
converters,
addDefaultConverters
? getDefaultConverters()
: Collections.emptyList());
The converters
collection contains the scanned HttpMessageConverter
(s).
Based on the environment.
That list is then joined with a default one provided by WebMvcConfigurationSupport
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
private static final boolean romePresent;
private static final boolean jaxb2Present;
private static final boolean jackson2Present;
private static final boolean jackson2XmlPresent;
private static final boolean jackson2SmilePresent;
private static final boolean jackson2CborPresent;
private static final boolean gsonPresent;
private static final boolean jsonbPresent;
...
Infact the documentations for WebMvcConfigurationSupport
states
This class registers ... ... a range of
HttpMessageConverters
depending on the third-party libraries available on the classpath.
The scanned HttpMessageConverter
(s) are found and instantiated via HttpMessageConvertersAutoConfiguration
, whose documentation is
Auto-configuration for
HttpMessageConverters
.
That class exposes by itself a StringHttpMessageConverter
@Bean
@ConditionalOnMissingBean
public StringHttpMessageConverter stringHttpMessageConverter() {
StringHttpMessageConverter converter = new StringHttpMessageConverter(
this.properties.getCharset());
converter.setWriteAcceptCharset(false);
return converter;
}
Than, it imports Jackson or Gson auto-configurations
@Import({
JacksonHttpMessageConvertersConfiguration.class
GsonHttpMessageConvertersConfiguration.class,
JsonbHttpMessageConvertersConfiguration.class
})
And that's how those environment-based ones are "summed" to the pre-defined ones.
Spring doesn't get confused by duplicates because it just takes the first which is compatible.
See how an HttpMessageConverter
is choosed
You can see it is just a simple for loop, and each convert is asked to say "can I do this?" via the canWrite
method
The first valid is picked.