Skip to content

Commit 7a94beb

Browse files
mp911dechristophstrobl
authored andcommitted
Allow RedisConnectionFactories to be initialized as part of the context lifecycle.
Lettuce and Jedis connection factories now can be configured to initialize early during afterPropertiesSet or configured whether the component should be auto-started by the container. By default, connection factories auto-startup early. Closes #2866 Original Pull Request: #2868
1 parent 779a012 commit 7a94beb

File tree

4 files changed

+180
-45
lines changed

4 files changed

+180
-45
lines changed

src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@
7878
* This connection factory implements {@link InitializingBean} and {@link SmartLifecycle} for flexible lifecycle
7979
* control. It must be {@link #afterPropertiesSet() initialized} and {@link #start() started} before you can obtain a
8080
* connection. {@link #afterPropertiesSet() Initialization} {@link SmartLifecycle#start() starts} this bean
81-
* {@link #isAutoStartup() by default}. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start() restart}
82-
* this connection factory if needed.
81+
* {@link #isEarlyStartup() early} by default. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start()
82+
* restart} this connection factory if needed. Disabling {@link #isEarlyStartup() early startup} leaves lifecycle
83+
* management to the container refresh if {@link #isAutoStartup() auto-startup} is enabled.
8384
* <p>
8485
* Note that {@link JedisConnection} and its {@link JedisClusterConnection clustered variant} are not Thread-safe and
8586
* instances should not be shared across threads. Refer to the
@@ -103,9 +104,10 @@ public class JedisConnectionFactory
103104
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
104105
JedisExceptionConverter.INSTANCE);
105106

106-
private boolean convertPipelineAndTxResults = true;
107-
108107
private int phase = 0; // in between min and max values
108+
private boolean autoStartup = true;
109+
private boolean earlyStartup = true;
110+
private boolean convertPipelineAndTxResults = true;
109111

110112
private final AtomicReference<State> state = new AtomicReference<>(State.CREATED);
111113

@@ -571,6 +573,70 @@ public RedisClusterConfiguration getClusterConfiguration() {
571573
return RedisConfiguration.isClusterConfiguration(configuration) ? (RedisClusterConfiguration) configuration : null;
572574
}
573575

576+
@Override
577+
public int getPhase() {
578+
return this.phase;
579+
}
580+
581+
/**
582+
* Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}.
583+
*
584+
* @since 3.2
585+
* @see SmartLifecycle#getPhase()
586+
*/
587+
public void setPhase(int phase) {
588+
this.phase = phase;
589+
}
590+
591+
/**
592+
* @since 3.3
593+
*/
594+
@Override
595+
public boolean isAutoStartup() {
596+
return this.autoStartup;
597+
}
598+
599+
/**
600+
* Configure if this Lifecycle connection factory should get started automatically by the container at the time that
601+
* the containing ApplicationContext gets refreshed.
602+
* <p>
603+
* This connection factory defaults to early auto-startup during {@link #afterPropertiesSet()} and can potentially
604+
* create Redis connections early on in the lifecycle. See {@link #setEarlyStartup(boolean)} for delaying connection
605+
* creation to the ApplicationContext refresh if auto-startup is enabled.
606+
*
607+
* @param autoStartup {@literal true} to automatically {@link #start()} the connection factory; {@literal false}
608+
* otherwise.
609+
* @since 3.3
610+
* @see #setEarlyStartup(boolean)
611+
* @see #start()
612+
*/
613+
public void setAutoStartup(boolean autoStartup) {
614+
this.autoStartup = autoStartup;
615+
}
616+
617+
/**
618+
* @return whether to {@link #start()} the component during {@link #afterPropertiesSet()}.
619+
* @since 3.3
620+
*/
621+
public boolean isEarlyStartup() {
622+
return this.earlyStartup;
623+
}
624+
625+
/**
626+
* Configure if this InitializingBean's component Lifecycle should get started early by {@link #afterPropertiesSet()}
627+
* at the time that the bean is initialized. The component defaults to auto-startup.
628+
* <p>
629+
* This method is related to {@link #setAutoStartup(boolean) auto-startup} and can be used to delay Redis client
630+
* startup until the ApplicationContext refresh. Disabling early startup does not disable auto-startup.
631+
*
632+
* @param earlyStartup {@literal true} to early {@link #start()} the component; {@literal false} otherwise.
633+
* @since 3.3
634+
* @see #setAutoStartup(boolean)
635+
*/
636+
public void setEarlyStartup(boolean earlyStartup) {
637+
this.earlyStartup = earlyStartup;
638+
}
639+
574640
/**
575641
* Specifies if pipelined results should be converted to the expected data type. If {@code false}, results of
576642
* {@link JedisConnection#closePipeline()} and {@link JedisConnection#exec()} will be of the type returned by the
@@ -616,7 +682,7 @@ public void afterPropertiesSet() {
616682

617683
this.clientConfig = createClientConfig(getDatabase(), getRedisUsername(), getRedisPassword());
618684

619-
if (isAutoStartup()) {
685+
if (isEarlyStartup()) {
620686
start();
621687
}
622688
}
@@ -724,21 +790,6 @@ public void stop() {
724790
}
725791
}
726792

727-
@Override
728-
public int getPhase() {
729-
return this.phase;
730-
}
731-
732-
/**
733-
* Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}.
734-
*
735-
* @since 3.2
736-
* @see SmartLifecycle#getPhase()
737-
*/
738-
public void setPhase(int phase) {
739-
this.phase = phase;
740-
}
741-
742793
@Override
743794
public boolean isRunning() {
744795
return State.STARTED.equals(this.state.get());

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@
100100
* This connection factory implements {@link InitializingBean} and {@link SmartLifecycle} for flexible lifecycle
101101
* control. It must be {@link #afterPropertiesSet() initialized} and {@link #start() started} before you can obtain a
102102
* connection. {@link #afterPropertiesSet() Initialization} {@link SmartLifecycle#start() starts} this bean
103-
* {@link #isAutoStartup() by default}. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start() restart}
104-
* this connection factory if needed.
103+
* {@link #isEarlyStartup() early} by default. You can {@link SmartLifecycle#stop()} and {@link SmartLifecycle#start()
104+
* restart} this connection factory if needed. Disabling {@link #isEarlyStartup() early startup} leaves lifecycle
105+
* management to the container refresh if {@link #isAutoStartup() auto-startup} is enabled.
105106
*
106107
* @author Costin Leau
107108
* @author Jennifer Hickey
@@ -121,13 +122,14 @@ public class LettuceConnectionFactory implements RedisConnectionFactory, Reactiv
121122
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
122123
LettuceExceptionConverter.INSTANCE);
123124

125+
private int phase = 0; // in between min and max values
126+
private boolean autoStartup = true;
127+
private boolean earlyStartup = true;
124128
private boolean convertPipelineAndTxResults = true;
125129
private boolean eagerInitialization = false;
130+
126131
private boolean shareNativeConnection = true;
127132
private boolean validateConnection = false;
128-
129-
private int phase = 0; // in between min and max values
130-
131133
private @Nullable AbstractRedisClient client;
132134

133135
private final AtomicReference<State> state = new AtomicReference<>(State.CREATED);
@@ -556,12 +558,13 @@ public void setShareNativeConnection(boolean shareNativeConnection) {
556558

557559
/**
558560
* Indicates {@link #setShareNativeConnection(boolean) shared connections} should be eagerly initialized. Eager
559-
* initialization requires a running Redis instance during application startup to allow early validation of connection
560-
* factory configuration. Eager initialization also prevents blocking connect while using reactive API and is
561-
* recommended for reactive API usage.
561+
* initialization requires a running Redis instance during {@link #start() startup} to allow early validation of
562+
* connection factory configuration. Eager initialization also prevents blocking connect while using reactive API and
563+
* is recommended for reactive API usage.
562564
*
563-
* @return {@link true} if the shared connection is initialized upon {@link #afterPropertiesSet()}.
565+
* @return {@link true} if the shared connection is initialized upon {@link #start()}.
564566
* @since 2.2
567+
* @see #start()
565568
*/
566569
public boolean getEagerInitialization() {
567570
return this.eagerInitialization;
@@ -795,6 +798,70 @@ public RedisClusterConfiguration getClusterConfiguration() {
795798
return isClusterAware() ? (RedisClusterConfiguration) this.configuration : null;
796799
}
797800

801+
@Override
802+
public int getPhase() {
803+
return this.phase;
804+
}
805+
806+
/**
807+
* Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}.
808+
*
809+
* @since 3.2
810+
* @see SmartLifecycle#getPhase()
811+
*/
812+
public void setPhase(int phase) {
813+
this.phase = phase;
814+
}
815+
816+
/**
817+
* @since 3.3
818+
*/
819+
@Override
820+
public boolean isAutoStartup() {
821+
return this.autoStartup;
822+
}
823+
824+
/**
825+
* Configure if this Lifecycle connection factory should get started automatically by the container at the time that
826+
* the containing ApplicationContext gets refreshed.
827+
* <p>
828+
* This connection factory defaults to early auto-startup during {@link #afterPropertiesSet()} and can potentially
829+
* create Redis connections early on in the lifecycle. See {@link #setEarlyStartup(boolean)} for delaying connection
830+
* creation to the ApplicationContext refresh if auto-startup is enabled.
831+
*
832+
* @param autoStartup {@literal true} to automatically {@link #start()} the connection factory; {@literal false}
833+
* otherwise.
834+
* @since 3.3
835+
* @see #setEarlyStartup(boolean)
836+
* @see #start()
837+
*/
838+
public void setAutoStartup(boolean autoStartup) {
839+
this.autoStartup = autoStartup;
840+
}
841+
842+
/**
843+
* @return whether to {@link #start()} the component during {@link #afterPropertiesSet()}.
844+
* @since 3.3
845+
*/
846+
public boolean isEarlyStartup() {
847+
return this.earlyStartup;
848+
}
849+
850+
/**
851+
* Configure if this InitializingBean's component Lifecycle should get started early by {@link #afterPropertiesSet()}
852+
* at the time that the bean is initialized. The component defaults to auto-startup.
853+
* <p>
854+
* This method is related to {@link #setAutoStartup(boolean) auto-startup} and can be used to delay Redis client
855+
* startup until the ApplicationContext refresh. Disabling early startup does not disable auto-startup.
856+
*
857+
* @param earlyStartup {@literal true} to early {@link #start()} the component; {@literal false} otherwise.
858+
* @since 3.3
859+
* @see #setAutoStartup(boolean)
860+
*/
861+
public void setEarlyStartup(boolean earlyStartup) {
862+
this.earlyStartup = earlyStartup;
863+
}
864+
798865
/**
799866
* Specifies if pipelined results should be converted to the expected data type. If {@code false}, results of
800867
* {@link LettuceConnection#closePipeline()} and {LettuceConnection#exec()} will be of the type returned by the
@@ -924,21 +991,6 @@ public void stop() {
924991
state.set(State.STOPPED);
925992
}
926993

927-
@Override
928-
public int getPhase() {
929-
return this.phase;
930-
}
931-
932-
/**
933-
* Specify the lifecycle phase for pausing and resuming this executor. The default is {@code 0}.
934-
*
935-
* @since 3.2
936-
* @see SmartLifecycle#getPhase()
937-
*/
938-
public void setPhase(int phase) {
939-
this.phase = phase;
940-
}
941-
942994
@Override
943995
public boolean isRunning() {
944996
return State.STARTED.equals(this.state.get());
@@ -947,7 +999,7 @@ public boolean isRunning() {
947999
@Override
9481000
public void afterPropertiesSet() {
9491001

950-
if (isAutoStartup()) {
1002+
if (isEarlyStartup()) {
9511003
start();
9521004
}
9531005
}

src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
3737
import org.junit.jupiter.api.Test;
38+
3839
import org.springframework.data.redis.connection.RedisClusterConfiguration;
3940
import org.springframework.data.redis.connection.RedisPassword;
4041
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
@@ -337,6 +338,21 @@ void afterPropertiesTriggersConnectionInitialization() {
337338
assertThat(connectionFactory.isRunning()).isTrue();
338339
}
339340

341+
@Test // GH-2866
342+
void earlyStartupDoesNotStartConnectionFactory() {
343+
344+
JedisConnectionFactory connectionFactory = new JedisConnectionFactory(new JedisPoolConfig());
345+
346+
connectionFactory.setEarlyStartup(false);
347+
connectionFactory.afterPropertiesSet();
348+
349+
assertThat(connectionFactory.isEarlyStartup()).isFalse();
350+
assertThat(connectionFactory.isAutoStartup()).isTrue();
351+
assertThat(connectionFactory.isRunning()).isFalse();
352+
353+
assertThat(ReflectionTestUtils.getField(connectionFactory, "pool")).isNull();
354+
}
355+
340356
private JedisConnectionFactory initSpyedConnectionFactory(RedisSentinelConfiguration sentinelConfiguration,
341357
@Nullable JedisPoolConfig poolConfig) {
342358

src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryUnitTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,22 @@ void createRedisConfigurationWithValidRedisUriString() {
12601260
.extracting(RedisStandaloneConfiguration::getPort).isEqualTo(6789);
12611261
}
12621262

1263+
@Test // GH-2866
1264+
void earlyStartupDoesNotStartConnectionFactory() {
1265+
1266+
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(new RedisStandaloneConfiguration(),
1267+
LettuceTestClientConfiguration.defaultConfiguration());
1268+
connectionFactory.setEarlyStartup(false);
1269+
connectionFactory.afterPropertiesSet();
1270+
1271+
assertThat(connectionFactory.isEarlyStartup()).isFalse();
1272+
assertThat(connectionFactory.isAutoStartup()).isTrue();
1273+
assertThat(connectionFactory.isRunning()).isFalse();
1274+
1275+
AbstractRedisClient client = (AbstractRedisClient) getField(connectionFactory, "client");
1276+
assertThat(client).isNull();
1277+
}
1278+
12631279
static class CustomRedisConfiguration implements RedisConfiguration, WithHostAndPort {
12641280

12651281
private String hostName;

0 commit comments

Comments
 (0)