Skip to content

Commit 908a4d0

Browse files
christophstroblmp911de
authored andcommitted
Migrate RedisConnectionFactory to Lifecycle beans.
Closes: #2503 Original pull request: #2627
1 parent bc382ae commit 908a4d0

27 files changed

+622
-277
lines changed

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

+90-43
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.LinkedHashSet;
3535
import java.util.Optional;
3636
import java.util.Set;
37+
import java.util.concurrent.atomic.AtomicReference;
3738

3839
import javax.net.ssl.HostnameVerifier;
3940
import javax.net.ssl.SSLParameters;
@@ -42,9 +43,9 @@
4243
import org.apache.commons.logging.Log;
4344
import org.apache.commons.logging.LogFactory;
4445
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
45-
4646
import org.springframework.beans.factory.DisposableBean;
4747
import org.springframework.beans.factory.InitializingBean;
48+
import org.springframework.context.SmartLifecycle;
4849
import org.springframework.dao.DataAccessException;
4950
import org.springframework.dao.InvalidDataAccessApiUsageException;
5051
import org.springframework.dao.InvalidDataAccessResourceUsageException;
@@ -85,7 +86,7 @@
8586
* @see JedisClientConfiguration
8687
* @see Jedis
8788
*/
88-
public class JedisConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {
89+
public class JedisConnectionFactory implements RedisConnectionFactory, InitializingBean, DisposableBean, SmartLifecycle {
8990

9091
private final static Log log = LogFactory.getLog(JedisConnectionFactory.class);
9192
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
@@ -104,8 +105,11 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean,
104105
private @Nullable ClusterTopologyProvider topologyProvider;
105106
private @Nullable ClusterCommandExecutor clusterCommandExecutor;
106107

107-
private boolean initialized;
108-
private boolean destroyed;
108+
enum State {
109+
CREATED, STARTING, STARTED, STOPPING, STOPPED, DESTROYED;
110+
}
111+
112+
private AtomicReference<State> state = new AtomicReference<>(State.CREATED);
109113

110114
/**
111115
* Constructs a new {@link JedisConnectionFactory} instance with default settings (default connection pooling).
@@ -287,24 +291,80 @@ protected JedisConnection postProcessConnection(JedisConnection connection) {
287291
return connection;
288292
}
289293

290-
public void afterPropertiesSet() {
294+
@Override
295+
public void start() {
291296

292-
clientConfig = createClientConfig(getDatabase(), getRedisUsername(), getRedisPassword());
297+
State current = state.getAndUpdate(state -> {
298+
if (State.CREATED.equals(state) || State.STOPPED.equals(state)) {
299+
return State.STARTING;
300+
}
301+
return state;
302+
});
293303

294-
if (getUsePool() && !isRedisClusterAware()) {
295-
this.pool = createPool();
304+
if (State.CREATED.equals(current) || State.STOPPED.equals(current)) {
305+
306+
if (getUsePool() && !isRedisClusterAware()) {
307+
this.pool = createPool();
308+
}
309+
310+
if (isRedisClusterAware()) {
311+
312+
this.cluster = createCluster();
313+
this.topologyProvider = createTopologyProvider(this.cluster);
314+
this.clusterCommandExecutor = new ClusterCommandExecutor(this.topologyProvider,
315+
new JedisClusterConnection.JedisClusterNodeResourceProvider(this.cluster, this.topologyProvider),
316+
EXCEPTION_TRANSLATION);
317+
}
318+
319+
state.set(State.STARTED);
296320
}
321+
}
297322

298-
if (isRedisClusterAware()) {
323+
@Override
324+
public void stop() {
325+
326+
if (state.compareAndSet(State.STARTED, State.STOPPING)) {
327+
if (getUsePool() && !isRedisClusterAware()) {
328+
if (pool != null) {
329+
try {
330+
this.pool.close();
331+
} catch (Exception ex) {
332+
log.warn("Cannot properly close Jedis pool", ex);
333+
}
334+
this.pool = null;
335+
}
336+
}
337+
338+
if(this.clusterCommandExecutor != null) {
339+
try {
340+
this.clusterCommandExecutor.destroy();
341+
} catch (Exception e) {
342+
throw new RuntimeException(e);
343+
}
344+
}
299345

300-
this.cluster = createCluster();
301-
this.topologyProvider = createTopologyProvider(this.cluster);
302-
this.clusterCommandExecutor = new ClusterCommandExecutor(this.topologyProvider,
303-
new JedisClusterConnection.JedisClusterNodeResourceProvider(this.cluster, this.topologyProvider),
304-
EXCEPTION_TRANSLATION);
346+
if (this.cluster != null) {
347+
348+
this.topologyProvider = null;
349+
350+
try {
351+
cluster.close();
352+
} catch (Exception ex) {
353+
log.warn("Cannot properly close Jedis cluster", ex);
354+
}
355+
}
356+
state.set(State.STOPPED);
305357
}
358+
}
359+
360+
@Override
361+
public boolean isRunning() {
362+
return State.STARTED.equals(state.get());
363+
}
306364

307-
this.initialized = true;
365+
@Override
366+
public void afterPropertiesSet() {
367+
clientConfig = createClientConfig(getDatabase(), getRedisUsername(), getRedisPassword());
308368
}
309369

310370
JedisClientConfig createSentinelClientConfig(SentinelConfiguration sentinelConfiguration) {
@@ -415,32 +475,8 @@ protected JedisCluster createCluster(RedisClusterConfiguration clusterConfig,
415475

416476
public void destroy() {
417477

418-
if (getUsePool() && pool != null) {
419-
420-
try {
421-
pool.destroy();
422-
} catch (Exception ex) {
423-
log.warn("Cannot properly close Jedis pool", ex);
424-
}
425-
pool = null;
426-
}
427-
428-
if (cluster != null) {
429-
430-
try {
431-
cluster.close();
432-
} catch (Exception ex) {
433-
log.warn("Cannot properly close Jedis cluster", ex);
434-
}
435-
436-
try {
437-
clusterCommandExecutor.destroy();
438-
} catch (Exception ex) {
439-
log.warn("Cannot properly close cluster command executor", ex);
440-
}
441-
}
442-
443-
this.destroyed = true;
478+
stop();
479+
state.set(State.DESTROYED);
444480
}
445481

446482
public RedisConnection getConnection() {
@@ -866,8 +902,19 @@ private MutableJedisClientConfiguration getMutableConfiguration() {
866902
}
867903

868904
private void assertInitialized() {
869-
Assert.state(this.initialized, "JedisConnectionFactory was not initialized through afterPropertiesSet()");
870-
Assert.state(!this.destroyed, "JedisConnectionFactory was destroyed and cannot be used anymore");
905+
906+
State current = state.get();
907+
908+
if (State.STARTED.equals(current)) {
909+
return;
910+
}
911+
912+
switch (current) {
913+
case CREATED, STOPPED -> throw new IllegalStateException(String.format("JedisConnectionFactory has been %s. Use start() to initialize it", current));
914+
case DESTROYED -> throw new IllegalStateException(
915+
"JedisConnectionFactory was destroyed and cannot be used anymore");
916+
default -> throw new IllegalStateException(String.format("JedisConnectionFactory is %s", current));
917+
}
871918
}
872919

873920
/**

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

+83-40
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
import java.util.concurrent.CompletableFuture;
2727
import java.util.concurrent.CompletionStage;
2828
import java.util.concurrent.TimeUnit;
29+
import java.util.concurrent.atomic.AtomicReference;
2930
import java.util.function.Consumer;
3031
import java.util.stream.Collectors;
3132

3233
import org.springframework.beans.factory.DisposableBean;
3334
import org.springframework.beans.factory.InitializingBean;
35+
import org.springframework.context.SmartLifecycle;
3436
import org.springframework.dao.DataAccessException;
3537
import org.springframework.dao.InvalidDataAccessApiUsageException;
3638
import org.springframework.data.redis.ExceptionTranslationStrategy;
@@ -116,8 +118,8 @@
116118
* @author Andrea Como
117119
* @author Chris Bono
118120
*/
119-
public class LettuceConnectionFactory
120-
implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {
121+
public class LettuceConnectionFactory implements RedisConnectionFactory, ReactiveRedisConnectionFactory,
122+
InitializingBean, DisposableBean, SmartLifecycle {
121123

122124
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
123125
LettuceExceptionConverter.INSTANCE);
@@ -144,8 +146,11 @@ public class LettuceConnectionFactory
144146

145147
private @Nullable ClusterCommandExecutor clusterCommandExecutor;
146148

147-
private boolean initialized;
148-
private boolean destroyed;
149+
enum State {
150+
CREATED, STARTING, STARTED, STOPPING, STOPPED, DESTROYED;
151+
}
152+
153+
private AtomicReference<State> state = new AtomicReference<>(State.CREATED);
149154

150155
/**
151156
* Constructs a new {@link LettuceConnectionFactory} instance with default settings.
@@ -333,33 +338,78 @@ public static RedisConfiguration createRedisConfiguration(RedisURI redisUri) {
333338
return LettuceConverters.createRedisStandaloneConfiguration(redisUri);
334339
}
335340

336-
public void afterPropertiesSet() {
341+
@Override
342+
public void start() {
343+
344+
State current = state.getAndUpdate(state -> {
345+
if (State.CREATED.equals(state) || State.STOPPED.equals(state)) {
346+
return State.STARTING;
347+
}
348+
return state;
349+
});
337350

338-
this.client = createClient();
351+
if (State.CREATED.equals(current) || State.STOPPED.equals(current)) {
339352

340-
this.connectionProvider = new ExceptionTranslatingConnectionProvider(createConnectionProvider(client, CODEC));
341-
this.reactiveConnectionProvider = new ExceptionTranslatingConnectionProvider(
342-
createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC));
353+
this.client = createClient();
343354

344-
if (isClusterAware()) {
355+
this.connectionProvider = new ExceptionTranslatingConnectionProvider(createConnectionProvider(client, CODEC));
356+
this.reactiveConnectionProvider = new ExceptionTranslatingConnectionProvider(
357+
createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC));
358+
359+
if (isClusterAware()) {
360+
361+
this.clusterCommandExecutor = new ClusterCommandExecutor(
362+
new LettuceClusterTopologyProvider((RedisClusterClient) client),
363+
new LettuceClusterConnection.LettuceClusterNodeResourceProvider(this.connectionProvider),
364+
EXCEPTION_TRANSLATION);
365+
}
366+
367+
state.set(State.STARTED);
345368

346-
this.clusterCommandExecutor = new ClusterCommandExecutor(
347-
new LettuceClusterTopologyProvider((RedisClusterClient) client),
348-
new LettuceClusterConnection.LettuceClusterNodeResourceProvider(this.connectionProvider),
349-
EXCEPTION_TRANSLATION);
369+
if (getEagerInitialization() && getShareNativeConnection()) {
370+
initConnection();
371+
}
350372
}
373+
}
374+
375+
@Override
376+
public void stop() {
351377

352-
this.initialized = true;
378+
if (state.compareAndSet(State.STARTED, State.STOPPING)) {
379+
resetConnection();
380+
dispose(connectionProvider);
381+
dispose(reactiveConnectionProvider);
382+
try {
383+
Duration quietPeriod = clientConfiguration.getShutdownQuietPeriod();
384+
Duration timeout = clientConfiguration.getShutdownTimeout();
385+
client.shutdown(quietPeriod.toMillis(), timeout.toMillis(), TimeUnit.MILLISECONDS);
386+
state.set(State.STOPPED);
387+
} catch (Exception e) {
353388

354-
if (getEagerInitialization() && getShareNativeConnection()) {
355-
initConnection();
389+
if (log.isWarnEnabled()) {
390+
log.warn((client != null ? ClassUtils.getShortName(client.getClass()) : "LettuceClient")
391+
+ " did not shut down gracefully.", e);
392+
}
393+
}
394+
state.set(State.STOPPED);
356395
}
357396
}
358397

359-
public void destroy() {
398+
@Override
399+
public boolean isRunning() {
400+
return State.STARTED.equals(state.get());
401+
}
360402

361-
resetConnection();
403+
@Override
404+
public void afterPropertiesSet() {
405+
// customization hook. initialization happens in start
406+
}
362407

408+
@Override
409+
public void destroy() {
410+
411+
stop();
412+
client = null;
363413
if (clusterCommandExecutor != null) {
364414

365415
try {
@@ -368,23 +418,7 @@ public void destroy() {
368418
log.warn("Cannot properly close cluster command executor", ex);
369419
}
370420
}
371-
372-
dispose(connectionProvider);
373-
dispose(reactiveConnectionProvider);
374-
375-
try {
376-
Duration quietPeriod = clientConfiguration.getShutdownQuietPeriod();
377-
Duration timeout = clientConfiguration.getShutdownTimeout();
378-
client.shutdown(quietPeriod.toMillis(), timeout.toMillis(), TimeUnit.MILLISECONDS);
379-
} catch (Exception e) {
380-
381-
if (log.isWarnEnabled()) {
382-
log.warn((client != null ? ClassUtils.getShortName(client.getClass()) : "LettuceClient")
383-
+ " did not shut down gracefully.", e);
384-
}
385-
}
386-
387-
this.destroyed = true;
421+
state.set(State.DESTROYED);
388422
}
389423

390424
private void dispose(LettuceConnectionProvider connectionProvider) {
@@ -532,8 +566,6 @@ public void initConnection() {
532566
*/
533567
public void resetConnection() {
534568

535-
assertInitialized();
536-
537569
Optionals.toStream(Optional.ofNullable(connection), Optional.ofNullable(reactiveConnection))
538570
.forEach(SharedConnection::resetConnection);
539571

@@ -1267,8 +1299,19 @@ private RedisClient createBasicClient() {
12671299
}
12681300

12691301
private void assertInitialized() {
1270-
Assert.state(this.initialized, "LettuceConnectionFactory was not initialized through afterPropertiesSet()");
1271-
Assert.state(!this.destroyed, "LettuceConnectionFactory was destroyed and cannot be used anymore");
1302+
1303+
State current = state.get();
1304+
1305+
if (State.STARTED.equals(current)) {
1306+
return;
1307+
}
1308+
1309+
switch (current) {
1310+
case CREATED, STOPPED -> throw new IllegalStateException(String.format("LettuceConnectionFactory has been %s. Use start() to initialize it", current));
1311+
case DESTROYED -> throw new IllegalStateException(
1312+
"LettuceConnectionFactory was destroyed and cannot be used anymore");
1313+
default -> throw new IllegalStateException(String.format("LettuceConnectionFactory is %s", current));
1314+
}
12721315
}
12731316

12741317
private static void applyToAll(RedisURI source, Consumer<RedisURI> action) {

0 commit comments

Comments
 (0)