Skip to content

Commit f7ef393

Browse files
committed
Support virtual threads for Jackson pool.
1 parent fd465a0 commit f7ef393

File tree

2 files changed

+38
-2
lines changed

2 files changed

+38
-2
lines changed

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import com.fasterxml.jackson.annotation.JsonAutoDetect;
3232
import com.fasterxml.jackson.annotation.JsonCreator;
3333
import com.fasterxml.jackson.annotation.PropertyAccessor;
34+
import com.fasterxml.jackson.core.JsonFactory;
35+
import com.fasterxml.jackson.core.util.JsonRecyclerPools;
3436
import com.fasterxml.jackson.databind.Module;
3537
import com.fasterxml.jackson.databind.ObjectMapper;
3638
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
@@ -49,6 +51,7 @@
4951
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
5052
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
5153
import org.springframework.boot.autoconfigure.jackson.JacksonProperties.ConstructorDetectorStrategy;
54+
import org.springframework.boot.autoconfigure.thread.Threading;
5255
import org.springframework.boot.context.properties.EnableConfigurationProperties;
5356
import org.springframework.boot.jackson.JsonComponentModule;
5457
import org.springframework.boot.jackson.JsonMixinModule;
@@ -59,6 +62,7 @@
5962
import org.springframework.context.annotation.Primary;
6063
import org.springframework.context.annotation.Scope;
6164
import org.springframework.core.Ordered;
65+
import org.springframework.core.env.Environment;
6266
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
6367
import org.springframework.util.Assert;
6468
import org.springframework.util.ClassUtils;
@@ -127,8 +131,14 @@ static class JacksonObjectMapperConfiguration {
127131
@Bean
128132
@Primary
129133
@ConditionalOnMissingBean
130-
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
131-
return builder.createXmlMapper(false).build();
134+
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder, Environment environment) {
135+
builder = builder.createXmlMapper(false);
136+
JsonFactory factory = JsonFactory.builder().build();
137+
if (Threading.VIRTUAL.isActive(environment)) {
138+
factory.setRecyclerPool(JsonRecyclerPools.sharedLockFreePool());
139+
}
140+
141+
return builder.build();
132142
}
133143

134144
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@
2929
import com.fasterxml.jackson.annotation.JsonCreator.Mode;
3030
import com.fasterxml.jackson.annotation.JsonFormat;
3131
import com.fasterxml.jackson.annotation.JsonInclude;
32+
import com.fasterxml.jackson.core.JsonFactory;
3233
import com.fasterxml.jackson.core.JsonGenerator;
3334
import com.fasterxml.jackson.core.JsonParser;
3435
import com.fasterxml.jackson.core.ObjectCodec;
36+
import com.fasterxml.jackson.core.util.JsonRecyclerPools.LockFreePool;
37+
import com.fasterxml.jackson.core.util.JsonRecyclerPools.ThreadLocalPool;
3538
import com.fasterxml.jackson.databind.AnnotationIntrospector;
3639
import com.fasterxml.jackson.databind.DeserializationConfig;
3740
import com.fasterxml.jackson.databind.DeserializationFeature;
@@ -53,6 +56,8 @@
5356
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
5457
import org.assertj.core.api.InstanceOfAssertFactories;
5558
import org.junit.jupiter.api.Test;
59+
import org.junit.jupiter.api.condition.EnabledOnJre;
60+
import org.junit.jupiter.api.condition.JRE;
5661

5762
import org.springframework.aot.hint.RuntimeHints;
5863
import org.springframework.aot.hint.predicate.ReflectionHintsPredicates;
@@ -491,6 +496,27 @@ void shouldRegisterPropertyNamingStrategyHints() {
491496
"UPPER_CAMEL_CASE", "SNAKE_CASE", "UPPER_SNAKE_CASE", "LOWER_CASE", "KEBAB_CASE", "LOWER_DOT_CASE");
492497
}
493498

499+
@Test
500+
void shouldUseThreadLocalPool() {
501+
this.contextRunner.withPropertyValues("spring.threads.virtual.enabled:false").run((context) -> {
502+
ObjectMapper mapper = context.getBean(ObjectMapper.class);
503+
assertThat(mapper).extracting(ObjectMapper::getFactory)
504+
.extracting(JsonFactory::_getRecyclerPool)
505+
.isInstanceOf(ThreadLocalPool.class);
506+
});
507+
}
508+
509+
@Test
510+
@EnabledOnJre(JRE.JAVA_21)
511+
void shouldUseVirtualThread() {
512+
this.contextRunner.withPropertyValues("spring.threads.virtual.enabled:true").run((context) -> {
513+
ObjectMapper mapper = context.getBean(ObjectMapper.class);
514+
assertThat(mapper).extracting(ObjectMapper::getFactory)
515+
.extracting(JsonFactory::_getRecyclerPool)
516+
.isInstanceOf(LockFreePool.class);
517+
});
518+
}
519+
494520
private void shouldRegisterPropertyNamingStrategyHints(Class<?> type, String... fieldNames) {
495521
RuntimeHints hints = new RuntimeHints();
496522
new JacksonAutoConfigurationRuntimeHints().registerHints(hints, getClass().getClassLoader());

0 commit comments

Comments
 (0)