Commit ea1dc85d authored by Andy Wilkinson's avatar Andy Wilkinson

Isolate Jackson2ObjectMapperBuilder mutation

Previously, Jackson2ObjectMapperBuilder was a singleton bean. This
meant that if it was injected and mutated in one injection point,
usage in a subsequent injection point would see the previous
injection point's mutation which can lead to unexpected failures.

This commit updates the auto-configuration of the builder to make it
a protoype bean. Mutation of the builder that is intended to apply
globally should be made using a customizer.

Closes gh-17477
parent c7d2799f
...@@ -54,6 +54,7 @@ import org.springframework.context.ApplicationContext; ...@@ -54,6 +54,7 @@ import org.springframework.context.ApplicationContext;
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.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.Assert; import org.springframework.util.Assert;
...@@ -168,6 +169,7 @@ public class JacksonAutoConfiguration { ...@@ -168,6 +169,7 @@ public class JacksonAutoConfiguration {
static class JacksonObjectMapperBuilderConfiguration { static class JacksonObjectMapperBuilderConfiguration {
@Bean @Bean
@Scope("prototype")
@ConditionalOnMissingBean @ConditionalOnMissingBean
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext, Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) { List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
......
...@@ -391,6 +391,16 @@ class JacksonAutoConfigurationTests { ...@@ -391,6 +391,16 @@ class JacksonAutoConfigurationTests {
}); });
} }
@Test
void builderIsNotSharedAcrossMultipleInjectionPoints() {
this.contextRunner.withUserConfiguration(ObjectMapperBuilderConsumerConfig.class).run((context) -> {
ObjectMapperBuilderConsumerConfig consumer = context.getBean(ObjectMapperBuilderConsumerConfig.class);
assertThat(consumer.builderOne).isNotNull();
assertThat(consumer.builderTwo).isNotNull();
assertThat(consumer.builderOne).isNotSameAs(consumer.builderTwo);
});
}
private void assertParameterNamesModuleCreatorBinding(Mode expectedMode, Class<?>... configClasses) { private void assertParameterNamesModuleCreatorBinding(Mode expectedMode, Class<?>... configClasses) {
this.contextRunner.withUserConfiguration(configClasses).run((context) -> { this.contextRunner.withUserConfiguration(configClasses).run((context) -> {
DeserializationConfig deserializationConfig = context.getBean(ObjectMapper.class) DeserializationConfig deserializationConfig = context.getBean(ObjectMapper.class)
...@@ -479,6 +489,27 @@ class JacksonAutoConfigurationTests { ...@@ -479,6 +489,27 @@ class JacksonAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
static class ObjectMapperBuilderConsumerConfig {
Jackson2ObjectMapperBuilder builderOne;
Jackson2ObjectMapperBuilder builderTwo;
@Bean
String consumerOne(Jackson2ObjectMapperBuilder builder) {
this.builderOne = builder;
return "one";
}
@Bean
String consumerTwo(Jackson2ObjectMapperBuilder builder) {
this.builderTwo = builder;
return "two";
}
}
protected static final class Foo { protected static final class Foo {
private String name; private String name;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment