Revise RedisKeyValueAdapter to support lifecycle.
RedisKeyValueAdapter is now a lifecycle bean participating in Spring's SmartLifecycle support. Sticking to Lifecycle aligns with lifecycle support in RedisConnectionFactory where connections are stopped upon shutdown. Previously, RedisKeyValueAdapter stopped connections upon destroy() causing a delayed shutdown behavior that was out of sync with its RedisConnectionFactory. Closes: #2957 Original Pull Request: #2959
This commit is contained in:
committed by
Christoph Strobl
parent
bcb4fd1d74
commit
20688c5a26
@@ -26,12 +26,16 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
|
||||
import org.springframework.data.keyvalue.core.KeyValueAdapter;
|
||||
@@ -99,17 +103,19 @@ import org.springframework.util.ObjectUtils;
|
||||
* @author Mark Paluch
|
||||
* @author Andrey Muchnik
|
||||
* @author John Blum
|
||||
* @author Lucian Torje
|
||||
* @since 1.7
|
||||
*/
|
||||
public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
implements InitializingBean, ApplicationContextAware, ApplicationListener<RedisKeyspaceEvent> {
|
||||
implements InitializingBean, SmartLifecycle, ApplicationContextAware, ApplicationListener<RedisKeyspaceEvent> {
|
||||
|
||||
/**
|
||||
* Time To Live in seconds that phantom keys should live longer than the actual key.
|
||||
*/
|
||||
private static final int PHANTOM_KEY_TTL = 300;
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
private final AtomicReference<State> state = new AtomicReference<>(State.CREATED);
|
||||
|
||||
private RedisOperations<?, ?> redisOps;
|
||||
private RedisConverter converter;
|
||||
private @Nullable RedisMessageListenerContainer messageListenerContainer;
|
||||
@@ -121,6 +127,13 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
private @Nullable String keyspaceNotificationsConfigParameter = null;
|
||||
private ShadowCopy shadowCopy = ShadowCopy.DEFAULT;
|
||||
|
||||
/**
|
||||
* Lifecycle state of this factory.
|
||||
*/
|
||||
enum State {
|
||||
CREATED, STARTING, STARTED, STOPPING, STOPPED, DESTROYED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link RedisKeyValueAdapter} with default {@link RedisMappingContext} and default
|
||||
* {@link RedisCustomConversions}.
|
||||
@@ -202,7 +215,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
&& this.expirationListener.get() == null) {
|
||||
|
||||
if (rdo.getTimeToLive() != null && rdo.getTimeToLive() > 0) {
|
||||
initKeyExpirationListener();
|
||||
initKeyExpirationListener(this.messageListenerContainer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,6 +699,11 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
this.shadowCopy = shadowCopy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return State.STARTED.equals(this.state.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
* @since 1.8
|
||||
@@ -696,22 +714,61 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
if (this.managedListenerContainer) {
|
||||
initMessageListenerContainer();
|
||||
}
|
||||
}
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(EnableKeyspaceEvents.ON_STARTUP, this.enableKeyspaceEvents)) {
|
||||
initKeyExpirationListener();
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
State current = this.state.getAndUpdate(state -> isCreatedOrStopped(state) ? State.STARTING : state);
|
||||
|
||||
if (isCreatedOrStopped(current)) {
|
||||
|
||||
messageListenerContainer.start();
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(EnableKeyspaceEvents.ON_STARTUP, this.enableKeyspaceEvents)) {
|
||||
initKeyExpirationListener(this.messageListenerContainer);
|
||||
}
|
||||
|
||||
this.state.set(State.STARTED);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCreatedOrStopped(@Nullable State state) {
|
||||
return State.CREATED.equals(state) || State.STOPPED.equals(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
|
||||
if (state.compareAndSet(State.STARTED, State.STOPPING)) {
|
||||
|
||||
KeyExpirationEventMessageListener listener = this.expirationListener.get();
|
||||
if (listener != null) {
|
||||
|
||||
if (this.expirationListener.compareAndSet(listener, null)) {
|
||||
try {
|
||||
listener.destroy();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Could not destroy KeyExpirationEventMessageListener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageListenerContainer.stop();
|
||||
state.set(State.STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() throws Exception {
|
||||
|
||||
if (this.expirationListener.get() != null) {
|
||||
this.expirationListener.get().destroy();
|
||||
}
|
||||
stop();
|
||||
|
||||
if (this.managedListenerContainer && this.messageListenerContainer != null) {
|
||||
this.messageListenerContainer.destroy();
|
||||
this.messageListenerContainer = null;
|
||||
}
|
||||
|
||||
this.state.set(State.DESTROYED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -729,13 +786,12 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
|
||||
this.messageListenerContainer = new RedisMessageListenerContainer();
|
||||
this.messageListenerContainer.setConnectionFactory(((RedisTemplate<?, ?>) redisOps).getConnectionFactory());
|
||||
this.messageListenerContainer.afterPropertiesSet();
|
||||
this.messageListenerContainer.start();
|
||||
}
|
||||
|
||||
private void initKeyExpirationListener() {
|
||||
private void initKeyExpirationListener(RedisMessageListenerContainer messageListenerContainer) {
|
||||
|
||||
if (this.expirationListener.get() == null) {
|
||||
MappingExpirationListener listener = new MappingExpirationListener(this.messageListenerContainer, this.redisOps,
|
||||
MappingExpirationListener listener = new MappingExpirationListener(messageListenerContainer, this.redisOps,
|
||||
this.converter, this.shadowCopy);
|
||||
|
||||
listener.setKeyspaceNotificationsConfigParameter(keyspaceNotificationsConfigParameter);
|
||||
|
||||
Reference in New Issue
Block a user