diff --git a/pom.xml b/pom.xml index 507b76dd15..219f179eeb 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-redis - 3.3.0-SNAPSHOT + 3.3.0-GH-2866-SNAPSHOT Spring Data Redis Spring Data module for Redis diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java index f9c8215d9d..4e83c271cf 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java @@ -78,8 +78,9 @@ * This connection factory implements {@link InitializingBean} and {@link SmartLifecycle} for flexible lifecycle * control. It must be {@link #afterPropertiesSet() initialized} and {@link #start() started} before you can obtain a * connection. {@link #afterPropertiesSet() Initialization} {@link SmartLifecycle#start() starts} this bean - * {@link #isAutoStartup() by default}. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start() restart} - * this connection factory if needed. + * {@link #isEarlyStartup() early} by default. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start() + * restart} this connection factory if needed. Disabling {@link #isEarlyStartup() early startup} leaves lifecycle + * management to the container refresh if {@link #isAutoStartup() auto-startup} is enabled. *

* Note that {@link JedisConnection} and its {@link JedisClusterConnection clustered variant} are not Thread-safe and * instances should not be shared across threads. Refer to the @@ -103,9 +104,10 @@ public class JedisConnectionFactory private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy( JedisExceptionConverter.INSTANCE); - private boolean convertPipelineAndTxResults = true; - private int phase = 0; // in between min and max values + private boolean autoStartup = true; + private boolean earlyStartup = true; + private boolean convertPipelineAndTxResults = true; private final AtomicReference state = new AtomicReference<>(State.CREATED); @@ -571,6 +573,70 @@ public RedisClusterConfiguration getClusterConfiguration() { return RedisConfiguration.isClusterConfiguration(configuration) ? (RedisClusterConfiguration) configuration : null; } + @Override + public int getPhase() { + return this.phase; + } + + /** + * Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}. + * + * @since 3.2 + * @see SmartLifecycle#getPhase() + */ + public void setPhase(int phase) { + this.phase = phase; + } + + /** + * @since 3.3 + */ + @Override + public boolean isAutoStartup() { + return this.autoStartup; + } + + /** + * Configure if this Lifecycle connection factory should get started automatically by the container at the time that + * the containing ApplicationContext gets refreshed. + *

+ * This connection factory defaults to early auto-startup during {@link #afterPropertiesSet()} and can potentially + * create Redis connections early on in the lifecycle. See {@link #setEarlyStartup(boolean)} for delaying connection + * creation to the ApplicationContext refresh if auto-startup is enabled. + * + * @param autoStartup {@literal true} to automatically {@link #start()} the connection factory; {@literal false} + * otherwise. + * @since 3.3 + * @see #setEarlyStartup(boolean) + * @see #start() + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** + * @return whether to {@link #start()} the component during {@link #afterPropertiesSet()}. + * @since 3.3 + */ + public boolean isEarlyStartup() { + return this.earlyStartup; + } + + /** + * Configure if this InitializingBean's component Lifecycle should get started early by {@link #afterPropertiesSet()} + * at the time that the bean is initialized. The component defaults to auto-startup. + *

+ * This method is related to {@link #setAutoStartup(boolean) auto-startup} and can be used to delay Redis client + * startup until the ApplicationContext refresh. Disabling early startup does not disable auto-startup. + * + * @param earlyStartup {@literal true} to early {@link #start()} the component; {@literal false} otherwise. + * @since 3.3 + * @see #setAutoStartup(boolean) + */ + public void setEarlyStartup(boolean earlyStartup) { + this.earlyStartup = earlyStartup; + } + /** * Specifies if pipelined results should be converted to the expected data type. If {@code false}, results of * {@link JedisConnection#closePipeline()} and {@link JedisConnection#exec()} will be of the type returned by the @@ -616,7 +682,7 @@ public void afterPropertiesSet() { this.clientConfig = createClientConfig(getDatabase(), getRedisUsername(), getRedisPassword()); - if (isAutoStartup()) { + if (isEarlyStartup()) { start(); } } @@ -724,21 +790,6 @@ public void stop() { } } - @Override - public int getPhase() { - return this.phase; - } - - /** - * Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}. - * - * @since 3.2 - * @see SmartLifecycle#getPhase() - */ - public void setPhase(int phase) { - this.phase = phase; - } - @Override public boolean isRunning() { return State.STARTED.equals(this.state.get()); diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java index 2b6ac76503..c406faaa1e 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java @@ -100,8 +100,9 @@ * This connection factory implements {@link InitializingBean} and {@link SmartLifecycle} for flexible lifecycle * control. It must be {@link #afterPropertiesSet() initialized} and {@link #start() started} before you can obtain a * connection. {@link #afterPropertiesSet() Initialization} {@link SmartLifecycle#start() starts} this bean - * {@link #isAutoStartup() by default}. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start() restart} - * this connection factory if needed. + * {@link #isEarlyStartup() early} by default. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start() + * restart} this connection factory if needed. Disabling {@link #isEarlyStartup() early startup} leaves lifecycle + * management to the container refresh if {@link #isAutoStartup() auto-startup} is enabled. * * @author Costin Leau * @author Jennifer Hickey @@ -121,13 +122,14 @@ public class LettuceConnectionFactory implements RedisConnectionFactory, Reactiv private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy( LettuceExceptionConverter.INSTANCE); + private int phase = 0; // in between min and max values + private boolean autoStartup = true; + private boolean earlyStartup = true; private boolean convertPipelineAndTxResults = true; private boolean eagerInitialization = false; + private boolean shareNativeConnection = true; private boolean validateConnection = false; - - private int phase = 0; // in between min and max values - private @Nullable AbstractRedisClient client; private final AtomicReference state = new AtomicReference<>(State.CREATED); @@ -556,12 +558,13 @@ public void setShareNativeConnection(boolean shareNativeConnection) { /** * Indicates {@link #setShareNativeConnection(boolean) shared connections} should be eagerly initialized. Eager - * initialization requires a running Redis instance during application startup to allow early validation of connection - * factory configuration. Eager initialization also prevents blocking connect while using reactive API and is - * recommended for reactive API usage. + * initialization requires a running Redis instance during {@link #start() startup} to allow early validation of + * connection factory configuration. Eager initialization also prevents blocking connect while using reactive API and + * is recommended for reactive API usage. * - * @return {@link true} if the shared connection is initialized upon {@link #afterPropertiesSet()}. + * @return {@link true} if the shared connection is initialized upon {@link #start()}. * @since 2.2 + * @see #start() */ public boolean getEagerInitialization() { return this.eagerInitialization; @@ -795,6 +798,70 @@ public RedisClusterConfiguration getClusterConfiguration() { return isClusterAware() ? (RedisClusterConfiguration) this.configuration : null; } + @Override + public int getPhase() { + return this.phase; + } + + /** + * Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}. + * + * @since 3.2 + * @see SmartLifecycle#getPhase() + */ + public void setPhase(int phase) { + this.phase = phase; + } + + /** + * @since 3.3 + */ + @Override + public boolean isAutoStartup() { + return this.autoStartup; + } + + /** + * Configure if this Lifecycle connection factory should get started automatically by the container at the time that + * the containing ApplicationContext gets refreshed. + *

+ * This connection factory defaults to early auto-startup during {@link #afterPropertiesSet()} and can potentially + * create Redis connections early on in the lifecycle. See {@link #setEarlyStartup(boolean)} for delaying connection + * creation to the ApplicationContext refresh if auto-startup is enabled. + * + * @param autoStartup {@literal true} to automatically {@link #start()} the connection factory; {@literal false} + * otherwise. + * @since 3.3 + * @see #setEarlyStartup(boolean) + * @see #start() + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** + * @return whether to {@link #start()} the component during {@link #afterPropertiesSet()}. + * @since 3.3 + */ + public boolean isEarlyStartup() { + return this.earlyStartup; + } + + /** + * Configure if this InitializingBean's component Lifecycle should get started early by {@link #afterPropertiesSet()} + * at the time that the bean is initialized. The component defaults to auto-startup. + *

+ * This method is related to {@link #setAutoStartup(boolean) auto-startup} and can be used to delay Redis client + * startup until the ApplicationContext refresh. Disabling early startup does not disable auto-startup. + * + * @param earlyStartup {@literal true} to early {@link #start()} the component; {@literal false} otherwise. + * @since 3.3 + * @see #setAutoStartup(boolean) + */ + public void setEarlyStartup(boolean earlyStartup) { + this.earlyStartup = earlyStartup; + } + /** * Specifies if pipelined results should be converted to the expected data type. If {@code false}, results of * {@link LettuceConnection#closePipeline()} and {LettuceConnection#exec()} will be of the type returned by the @@ -924,21 +991,6 @@ public void stop() { state.set(State.STOPPED); } - @Override - public int getPhase() { - return this.phase; - } - - /** - * Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}. - * - * @since 3.2 - * @see SmartLifecycle#getPhase() - */ - public void setPhase(int phase) { - this.phase = phase; - } - @Override public boolean isRunning() { return State.STARTED.equals(this.state.get()); @@ -947,7 +999,7 @@ public boolean isRunning() { @Override public void afterPropertiesSet() { - if (isAutoStartup()) { + if (isEarlyStartup()) { start(); } } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java index 950a09aacc..f6e3f3f9e4 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java @@ -35,6 +35,7 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.junit.jupiter.api.Test; + import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisSentinelConfiguration; @@ -337,6 +338,21 @@ void afterPropertiesTriggersConnectionInitialization() { assertThat(connectionFactory.isRunning()).isTrue(); } + @Test // GH-2866 + void earlyStartupDoesNotStartConnectionFactory() { + + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(new JedisPoolConfig()); + + connectionFactory.setEarlyStartup(false); + connectionFactory.afterPropertiesSet(); + + assertThat(connectionFactory.isEarlyStartup()).isFalse(); + assertThat(connectionFactory.isAutoStartup()).isTrue(); + assertThat(connectionFactory.isRunning()).isFalse(); + + assertThat(ReflectionTestUtils.getField(connectionFactory, "pool")).isNull(); + } + private JedisConnectionFactory initSpyedConnectionFactory(RedisSentinelConfiguration sentinelConfiguration, @Nullable JedisPoolConfig poolConfig) { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryUnitTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryUnitTests.java index 2c29c12e52..b4dd713fbe 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryUnitTests.java @@ -1260,6 +1260,22 @@ void createRedisConfigurationWithValidRedisUriString() { .extracting(RedisStandaloneConfiguration::getPort).isEqualTo(6789); } + @Test // GH-2866 + void earlyStartupDoesNotStartConnectionFactory() { + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(new RedisStandaloneConfiguration(), + LettuceTestClientConfiguration.defaultConfiguration()); + connectionFactory.setEarlyStartup(false); + connectionFactory.afterPropertiesSet(); + + assertThat(connectionFactory.isEarlyStartup()).isFalse(); + assertThat(connectionFactory.isAutoStartup()).isTrue(); + assertThat(connectionFactory.isRunning()).isFalse(); + + AbstractRedisClient client = (AbstractRedisClient) getField(connectionFactory, "client"); + assertThat(client).isNull(); + } + static class CustomRedisConfiguration implements RedisConfiguration, WithHostAndPort { private String hostName;