Skip to content

Commit ea1dc85

Browse files
committed
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
1 parent c7d2799 commit ea1dc85

File tree

2 files changed

+33
-0
lines changed

2 files changed

+33
-0
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.springframework.context.annotation.Bean;
5555
import org.springframework.context.annotation.Configuration;
5656
import org.springframework.context.annotation.Primary;
57+
import org.springframework.context.annotation.Scope;
5758
import org.springframework.core.Ordered;
5859
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
5960
import org.springframework.util.Assert;
@@ -168,6 +169,7 @@ ParameterNamesModule parameterNamesModule() {
168169
static class JacksonObjectMapperBuilderConfiguration {
169170

170171
@Bean
172+
@Scope("prototype")
171173
@ConditionalOnMissingBean
172174
Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext,
173175
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,16 @@ void writeWithVisibility() {
391391
});
392392
}
393393

394+
@Test
395+
void builderIsNotSharedAcrossMultipleInjectionPoints() {
396+
this.contextRunner.withUserConfiguration(ObjectMapperBuilderConsumerConfig.class).run((context) -> {
397+
ObjectMapperBuilderConsumerConfig consumer = context.getBean(ObjectMapperBuilderConsumerConfig.class);
398+
assertThat(consumer.builderOne).isNotNull();
399+
assertThat(consumer.builderTwo).isNotNull();
400+
assertThat(consumer.builderOne).isNotSameAs(consumer.builderTwo);
401+
});
402+
}
403+
394404
private void assertParameterNamesModuleCreatorBinding(Mode expectedMode, Class<?>... configClasses) {
395405
this.contextRunner.withUserConfiguration(configClasses).run((context) -> {
396406
DeserializationConfig deserializationConfig = context.getBean(ObjectMapper.class)
@@ -479,6 +489,27 @@ Jackson2ObjectMapperBuilderCustomizer customDateFormat() {
479489

480490
}
481491

492+
@Configuration(proxyBeanMethods = false)
493+
static class ObjectMapperBuilderConsumerConfig {
494+
495+
Jackson2ObjectMapperBuilder builderOne;
496+
497+
Jackson2ObjectMapperBuilder builderTwo;
498+
499+
@Bean
500+
String consumerOne(Jackson2ObjectMapperBuilder builder) {
501+
this.builderOne = builder;
502+
return "one";
503+
}
504+
505+
@Bean
506+
String consumerTwo(Jackson2ObjectMapperBuilder builder) {
507+
this.builderTwo = builder;
508+
return "two";
509+
}
510+
511+
}
512+
482513
protected static final class Foo {
483514

484515
private String name;

0 commit comments

Comments
 (0)