From ea1dc85d50c1e2c86073b84b539515a4af511e9d Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 19 Jul 2019 13:57:55 +0100 Subject: [PATCH] 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 --- .../jackson/JacksonAutoConfiguration.java | 2 ++ .../JacksonAutoConfigurationTests.java | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java index efa3f8a775..79caab4a3e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java @@ -54,6 +54,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Scope; import org.springframework.core.Ordered; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.Assert; @@ -168,6 +169,7 @@ public class JacksonAutoConfiguration { static class JacksonObjectMapperBuilderConfiguration { @Bean + @Scope("prototype") @ConditionalOnMissingBean Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext, List customizers) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java index 35cccbcfc5..85f751f435 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java @@ -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) { this.contextRunner.withUserConfiguration(configClasses).run((context) -> { DeserializationConfig deserializationConfig = context.getBean(ObjectMapper.class) @@ -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 { private String name;