diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java index 80bcde7116..c1c6fbb6e8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/JedisConnectionConfiguration.java @@ -26,12 +26,15 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.ssl.SslOptions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisSentinelConfiguration; @@ -69,11 +72,23 @@ class JedisConnectionConfiguration extends RedisConnectionConfiguration { } @Bean + @ConditionalOnThreading(Threading.PLATFORM) JedisConnectionFactory redisConnectionFactory( ObjectProvider builderCustomizers) { return createJedisConnectionFactory(builderCustomizers); } + @Bean + @ConditionalOnThreading(Threading.VIRTUAL) + JedisConnectionFactory redisConnectionFactoryVirtualThreads( + ObjectProvider builderCustomizers) { + JedisConnectionFactory factory = createJedisConnectionFactory(builderCustomizers); + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-"); + executor.setVirtualThreads(true); + factory.setExecutor(executor); + return factory; + } + private JedisConnectionFactory createJedisConnectionFactory( ObjectProvider builderCustomizers) { JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(builderCustomizers); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java index 0f88d90296..4a765d1e20 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.java @@ -33,13 +33,16 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Lettuce.Cluster.Refresh; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.ssl.SslOptions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisSentinelConfiguration; @@ -83,9 +86,29 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration { @Bean @ConditionalOnMissingBean(RedisConnectionFactory.class) + @ConditionalOnThreading(Threading.PLATFORM) LettuceConnectionFactory redisConnectionFactory( ObjectProvider builderCustomizers, ClientResources clientResources) { + return createConnectionFactory(builderCustomizers, clientResources); + } + + @Bean + @ConditionalOnMissingBean(RedisConnectionFactory.class) + @ConditionalOnThreading(Threading.VIRTUAL) + LettuceConnectionFactory redisConnectionFactoryVirtualThreads( + ObjectProvider builderCustomizers, + ClientResources clientResources) { + LettuceConnectionFactory factory = createConnectionFactory(builderCustomizers, clientResources); + SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("redis-"); + executor.setVirtualThreads(true); + factory.setExecutor(executor); + return factory; + } + + private LettuceConnectionFactory createConnectionFactory( + ObjectProvider builderCustomizers, + ClientResources clientResources) { LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources, getProperties().getLettuce().getPool()); return createLettuceConnectionFactory(clientConfig); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java index 164e222fd8..2cbf2d5b06 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java @@ -19,14 +19,18 @@ package org.springframework.boot.autoconfigure.data.redis; import java.time.Duration; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.assertj.SimpleAsyncTaskExecutorAssert; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @@ -270,6 +274,25 @@ class RedisAutoConfigurationJedisTests { }); } + @Test + void shouldUsePlatformThreadsByDefault() { + this.contextRunner.run((context) -> { + JedisConnectionFactory factory = context.getBean(JedisConnectionFactory.class); + assertThat(factory).extracting("executor").isNull(); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldUseVirtualThreadsIfEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + JedisConnectionFactory factory = context.getBean(JedisConnectionFactory.class); + SimpleAsyncTaskExecutor executor = (SimpleAsyncTaskExecutor) ReflectionTestUtils.getField(factory, + "executor"); + SimpleAsyncTaskExecutorAssert.assertThat(executor).usesVirtualThreads(); + }); + } + private String getUserName(JedisConnectionFactory factory) { return ReflectionTestUtils.invokeMethod(factory, "getRedisUsername"); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index 3e6ab344b2..387ffe7da5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -31,6 +31,8 @@ import io.lettuce.core.resource.DefaultClientResources; import io.lettuce.core.tracing.Tracing; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool; @@ -38,8 +40,10 @@ import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.testsupport.assertj.SimpleAsyncTaskExecutorAssert; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisNode; @@ -582,6 +586,25 @@ class RedisAutoConfigurationTests { }); } + @Test + void shouldUsePlatformThreadsByDefault() { + this.contextRunner.run((context) -> { + LettuceConnectionFactory factory = context.getBean(LettuceConnectionFactory.class); + assertThat(factory).extracting("executor").isNull(); + }); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_21) + void shouldUseVirtualThreadsIfEnabled() { + this.contextRunner.withPropertyValues("spring.threads.virtual.enabled=true").run((context) -> { + LettuceConnectionFactory factory = context.getBean(LettuceConnectionFactory.class); + SimpleAsyncTaskExecutor executor = (SimpleAsyncTaskExecutor) ReflectionTestUtils.getField(factory, + "executor"); + SimpleAsyncTaskExecutorAssert.assertThat(executor).usesVirtualThreads(); + }); + } + private ContextConsumer assertClientOptions( Class expectedType, Consumer options) { return (context) -> {