2626import java .util .concurrent .TimeUnit ;
2727import java .util .concurrent .atomic .AtomicReference ;
2828
29+ import org .apache .commons .logging .Log ;
30+ import org .apache .commons .logging .LogFactory ;
31+
2932import org .springframework .beans .BeansException ;
3033import org .springframework .beans .factory .InitializingBean ;
3134import org .springframework .context .ApplicationContext ;
3235import org .springframework .context .ApplicationContextAware ;
3336import org .springframework .context .ApplicationEventPublisher ;
3437import org .springframework .context .ApplicationListener ;
38+ import org .springframework .context .SmartLifecycle ;
3539import org .springframework .core .convert .ConversionService ;
3640import org .springframework .data .keyvalue .core .AbstractKeyValueAdapter ;
3741import org .springframework .data .keyvalue .core .KeyValueAdapter ;
99103 * @author Mark Paluch
100104 * @author Andrey Muchnik
101105 * @author John Blum
102- * @author Lucian Torje
103106 * @since 1.7
104107 */
105108public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
106- implements InitializingBean , ApplicationContextAware , ApplicationListener <RedisKeyspaceEvent > {
109+ implements InitializingBean , SmartLifecycle , ApplicationContextAware , ApplicationListener <RedisKeyspaceEvent > {
107110
108111 /**
109112 * Time To Live in seconds that phantom keys should live longer than the actual key.
110113 */
111114 private static final int PHANTOM_KEY_TTL = 300 ;
112115
116+ private final Log logger = LogFactory .getLog (getClass ());
117+ private final AtomicReference <State > state = new AtomicReference <>(State .CREATED );
118+
113119 private RedisOperations <?, ?> redisOps ;
114120 private RedisConverter converter ;
115121 private @ Nullable RedisMessageListenerContainer messageListenerContainer ;
@@ -121,6 +127,13 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
121127 private @ Nullable String keyspaceNotificationsConfigParameter = null ;
122128 private ShadowCopy shadowCopy = ShadowCopy .DEFAULT ;
123129
130+ /**
131+ * Lifecycle state of this factory.
132+ */
133+ enum State {
134+ CREATED , STARTING , STARTED , STOPPING , STOPPED , DESTROYED ;
135+ }
136+
124137 /**
125138 * Creates new {@link RedisKeyValueAdapter} with default {@link RedisMappingContext} and default
126139 * {@link RedisCustomConversions}.
@@ -202,7 +215,7 @@ public Object put(Object id, Object item, String keyspace) {
202215 && this .expirationListener .get () == null ) {
203216
204217 if (rdo .getTimeToLive () != null && rdo .getTimeToLive () > 0 ) {
205- initKeyExpirationListener ();
218+ initKeyExpirationListener (this . messageListenerContainer );
206219 }
207220 }
208221
@@ -686,6 +699,11 @@ public void setShadowCopy(ShadowCopy shadowCopy) {
686699 this .shadowCopy = shadowCopy ;
687700 }
688701
702+ @ Override
703+ public boolean isRunning () {
704+ return State .STARTED .equals (this .state .get ());
705+ }
706+
689707 /**
690708 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
691709 * @since 1.8
@@ -696,22 +714,61 @@ public void afterPropertiesSet() {
696714 if (this .managedListenerContainer ) {
697715 initMessageListenerContainer ();
698716 }
717+ }
718+
719+ @ Override
720+ public void start () {
721+
722+ State current = this .state .getAndUpdate (state -> isCreatedOrStopped (state ) ? State .STARTING : state );
723+
724+ if (isCreatedOrStopped (current )) {
725+
726+ messageListenerContainer .start ();
727+
728+ if (ObjectUtils .nullSafeEquals (EnableKeyspaceEvents .ON_STARTUP , this .enableKeyspaceEvents )) {
729+ initKeyExpirationListener (this .messageListenerContainer );
730+ }
731+
732+ this .state .set (State .STARTED );
733+ }
734+ }
735+
736+ private static boolean isCreatedOrStopped (@ Nullable State state ) {
737+ return State .CREATED .equals (state ) || State .STOPPED .equals (state );
738+ }
739+
740+ @ Override
741+ public void stop () {
699742
700- if (ObjectUtils .nullSafeEquals (EnableKeyspaceEvents .ON_STARTUP , this .enableKeyspaceEvents )) {
701- initKeyExpirationListener ();
743+ if (state .compareAndSet (State .STARTED , State .STOPPING )) {
744+
745+ KeyExpirationEventMessageListener listener = this .expirationListener .get ();
746+ if (listener != null ) {
747+
748+ if (this .expirationListener .compareAndSet (listener , null )) {
749+ try {
750+ listener .destroy ();
751+ } catch (Exception e ) {
752+ logger .warn ("Could not destroy KeyExpirationEventMessageListener" , e );
753+ }
754+ }
755+ }
756+
757+ messageListenerContainer .stop ();
758+ state .set (State .STOPPED );
702759 }
703760 }
704761
705762 public void destroy () throws Exception {
706763
707- if (this .expirationListener .get () != null ) {
708- this .expirationListener .get ().destroy ();
709- }
764+ stop ();
710765
711766 if (this .managedListenerContainer && this .messageListenerContainer != null ) {
712767 this .messageListenerContainer .destroy ();
713768 this .messageListenerContainer = null ;
714769 }
770+
771+ this .state .set (State .DESTROYED );
715772 }
716773
717774 @ Override
@@ -729,13 +786,12 @@ private void initMessageListenerContainer() {
729786 this .messageListenerContainer = new RedisMessageListenerContainer ();
730787 this .messageListenerContainer .setConnectionFactory (((RedisTemplate <?, ?>) redisOps ).getConnectionFactory ());
731788 this .messageListenerContainer .afterPropertiesSet ();
732- this .messageListenerContainer .start ();
733789 }
734790
735- private void initKeyExpirationListener () {
791+ private void initKeyExpirationListener (RedisMessageListenerContainer messageListenerContainer ) {
736792
737793 if (this .expirationListener .get () == null ) {
738- MappingExpirationListener listener = new MappingExpirationListener (this . messageListenerContainer , this .redisOps ,
794+ MappingExpirationListener listener = new MappingExpirationListener (messageListenerContainer , this .redisOps ,
739795 this .converter , this .shadowCopy );
740796
741797 listener .setKeyspaceNotificationsConfigParameter (keyspaceNotificationsConfigParameter );
0 commit comments