1717
1818import java .util .Properties ;
1919
20+ import org .slf4j .Logger ;
21+ import org .slf4j .LoggerFactory ;
2022import org .springframework .beans .factory .DisposableBean ;
2123import org .springframework .beans .factory .InitializingBean ;
2224import org .springframework .data .redis .connection .Message ;
2325import org .springframework .data .redis .connection .MessageListener ;
2426import org .springframework .data .redis .connection .RedisConnection ;
27+ import org .springframework .data .redis .connection .RedisConnectionFactory ;
2528import org .springframework .lang .Nullable ;
2629import org .springframework .util .Assert ;
2730import org .springframework .util .ObjectUtils ;
2831import org .springframework .util .StringUtils ;
2932
3033/**
3134 * Base {@link MessageListener} implementation for listening to Redis keyspace notifications.
35+ * <p>
36+ * By default, this {@link MessageListener} does not listen for, or notify on, any keyspace events. You must explicitly
37+ * set the {@link #setKeyspaceNotificationsConfigParameter(String)} to a valid {@literal redis.conf},
38+ * {@literal notify-keyspace-events} value (for example: {@literal EA}) to enable keyspace event notifications
39+ * from your Redis server.
40+ * <p>
41+ * Any configuration set in the Redis server take precedence. Therefore, if the Redis server already set a value
42+ * for {@literal notify-keyspace-events}, then any {@link #setKeyspaceNotificationsConfigParameter(String)}
43+ * specified on this listener will be ignored.
44+ * <p>
45+ * It is recommended that all infrastructure settings, such as {@literal notify-keyspace-events}, be configured on
46+ * the Redis server itself. If the Redis server is rebooted, then any keyspace event configuration coming from
47+ * the application will be lost when the Redis server is restarted since Redis server configuration is not persistent,
48+ * and any configuration coming from your application only occurs during Spring container initialization.
3249 *
3350 * @author Christoph Strobl
3451 * @author Mark Paluch
52+ * @author John Blum
3553 * @since 1.7
3654 */
3755public abstract class KeyspaceEventMessageListener implements MessageListener , InitializingBean , DisposableBean {
3856
57+ protected static final String DISABLED_KEY_EVENTS = "" ;
58+ protected static final String NOTIFY_KEYSPACE_EVENTS = "notify-keyspace-events" ;
59+ 3960 private static final Topic TOPIC_ALL_KEYEVENTS = new PatternTopic ("__keyevent@*" );
4061
41- private final RedisMessageListenerContainer listenerContainer ;
62+ private final Logger logger = LoggerFactory .getLogger (getClass ());
63+ 64+ private final RedisMessageListenerContainer messageListenerContainer ;
65+ 66+ private @ Nullable String keyspaceNotificationsConfigParameter ;
67+ 68+ /**
69+ * Creates a new {@link KeyspaceEventMessageListener}.
70+ *
71+ * @param messageListenerContainer {@link RedisMessageListenerContainer} in which this listener will be registered;
72+ * must not be {@literal null}.
73+ */
74+ public KeyspaceEventMessageListener (RedisMessageListenerContainer messageListenerContainer ) {
75+ this (messageListenerContainer , DISABLED_KEY_EVENTS );
76+ }
77+ 78+ /**
79+ * Creates a new {@link KeyspaceEventMessageListener} along with initialization for
80+ * {@literal notify-keyspace-events}.
81+ *
82+ * @param messageListenerContainer {@link RedisMessageListenerContainer} in which this listener will be registered;
83+ * must not be {@literal null}.
84+ * @param keyspaceNotificationsConfigParameter {@link String default value} for {@literal notify-keyspace-events};
85+ * may be {@literal null}.
86+ */
87+ protected KeyspaceEventMessageListener (RedisMessageListenerContainer messageListenerContainer ,
88+ @ Nullable String keyspaceNotificationsConfigParameter ) {
89+ 90+ Assert .notNull (messageListenerContainer , "RedisMessageListenerContainer to run in must not be null" );
4291
43- private String keyspaceNotificationsConfigParameter = "EA" ;
92+ this .messageListenerContainer = messageListenerContainer ;
93+ this .keyspaceNotificationsConfigParameter = keyspaceNotificationsConfigParameter ;
94+ }
4495
4596 /**
46- * Creates new {@link KeyspaceEventMessageListener }.
97+ * Returns a reference to the configured {@link Logger }.
4798 *
48- * @param listenerContainer must not be {@literal null }.
99+ * @return a reference to the configured {@link Logger }.
49100 */
50- public KeyspaceEventMessageListener (RedisMessageListenerContainer listenerContainer ) {
101+ protected Logger getLogger () {
102+ return this .logger ;
103+ }
51104
52- Assert .notNull (listenerContainer , "RedisMessageListenerContainer to run in must not be null" );
53- this .listenerContainer = listenerContainer ;
105+ /**
106+ * Returns a configured reference to the {@link RedisMessageListenerContainer} to which this {@link MessageListener}
107+ * is registered.
108+ *
109+ * @return a configured reference to the {@link RedisMessageListenerContainer} to which this {@link MessageListener}
110+ * is registered.
111+ */
112+ protected RedisMessageListenerContainer getMessageListenerContainer () {
113+ return this .messageListenerContainer ;
54114 }
55115
56116 @ Override
57117 public void onMessage (Message message , @ Nullable byte [] pattern ) {
58118
59- if (ObjectUtils . isEmpty (message . getChannel ()) || ObjectUtils . isEmpty ( message . getBody () )) {
60- return ;
119+ if (containsChannelContent (message )) {
120+ doHandleMessage ( message ) ;
61121 }
122+ }
62123
63- doHandleMessage (message );
124+ // Message must have a channel and body (contain content)
125+ private boolean containsChannelContent (Message message ) {
126+ return !(ObjectUtils .isEmpty (message .getChannel ()) || ObjectUtils .isEmpty (message .getBody ()));
64127 }
65128
66129 /**
67- * Handle the actual message
130+ * Handle the actual {@link Message}.
68131 *
69- * @param message never {@literal null}.
132+ * @param message {@link Message} to process; never {@literal null}.
70133 */
71134 protected abstract void doHandleMessage (Message message );
72135
136+ @ Override
137+ public void afterPropertiesSet () throws Exception {
138+ init ();
139+ }
140+ 73141 /**
74- * Initialize the message listener by writing requried redis config for {@literal notify-keyspace-events} and
75- * registering the listener within the container.
142+ * Initialize this {@link MessageListener} by writing required Redis server config
143+ * for {@literal notify-keyspace-events} and registering this {@link MessageListener}
144+ * with the {@link RedisMessageListenerContainer}.
76145 */
77146 public void init () {
78147
79- if ( StringUtils . hasText ( keyspaceNotificationsConfigParameter )) {
148+ String keyspaceNotificationsConfigParameter = getKeyspaceNotificationsConfigParameter ();
80149
81- RedisConnection connection = listenerContainer .getConnectionFactory ().getConnection ();
150+ if (isSet (keyspaceNotificationsConfigParameter )) {
151+ configureKeyspaceEventNotifications (keyspaceNotificationsConfigParameter );
152+ }
82153
83- try {
154+ doRegister (getMessageListenerContainer ());
155+ }
84156
85- Properties config = connection .getConfig ("notify-keyspace-events" );
157+ private boolean isSet (@ Nullable String value ) {
158+ return StringUtils .hasText (value );
159+ }
86160
87- if (! StringUtils . hasText ( config . getProperty ( "notify-keyspace-events" )) ) {
88- connection . setConfig ( "notify-keyspace-events" , keyspaceNotificationsConfigParameter );
89- }
161+ void configureKeyspaceEventNotifications ( String keyspaceNotificationsConfigParameter ) {
162+ 163+ RedisConnectionFactory connectionFactory = getMessageListenerContainer (). getConnectionFactory ();
90164
91- } finally {
92- connection .close ();
165+ if (connectionFactory != null ) {
166+ try (RedisConnection connection = connectionFactory .getConnection ()) {
167+ if (canChangeNotifyKeyspaceEvents (connection )) {
168+ setKeyspaceEventNotifications (connection , keyspaceNotificationsConfigParameter );
169+ }
93170 }
94171 }
172+ else {
173+ if (getLogger ().isWarnEnabled ()) {
174+ getLogger ().warn ("Unable to configure notification on keyspace events;"
175+ + " no RedisConnectionFactory was configured in the RedisMessageListenerContainer" );
176+ }
177+ }
178+ }
179+ 180+ private boolean canChangeNotifyKeyspaceEvents (@ Nullable RedisConnection connection ) {
181+ 182+ if (connection != null ) {
183+ 184+ Properties config = connection .serverCommands ().getConfig (NOTIFY_KEYSPACE_EVENTS );
185+ 186+ return config == null || !isSet (config .getProperty (NOTIFY_KEYSPACE_EVENTS ));
187+ }
95188
96- doRegister (listenerContainer );
189+ return false ;
190+ }
191+ 192+ void setKeyspaceEventNotifications (RedisConnection connection , String keyspaceNotificationsConfigParameter ) {
193+ connection .serverCommands ().setConfig (NOTIFY_KEYSPACE_EVENTS , keyspaceNotificationsConfigParameter );
194+ }
195+ 196+ @ Override
197+ public void destroy () throws Exception {
198+ getMessageListenerContainer ().removeMessageListener (this );
97199 }
98200
99201 /**
100- * Register instance within the container .
202+ * Register instance within the {@link RedisMessageListenerContainer} .
101203 *
102204 * @param container never {@literal null}.
103205 */
104206 protected void doRegister (RedisMessageListenerContainer container ) {
105- listenerContainer .addMessageListener (this , TOPIC_ALL_KEYEVENTS );
106- }
107- 108- @ Override
109- public void destroy () throws Exception {
110- listenerContainer .removeMessageListener (this );
207+ container .addMessageListener (this , TOPIC_ALL_KEYEVENTS );
111208 }
112209
113210 /**
114- * Set the configuration string to use for {@literal notify-keyspace-events}.
211+ * Set the {@link String configuration setting} (for example: {@literal EA}) to use
212+ * for {@literal notify-keyspace-events}.
115213 *
116214 * @param keyspaceNotificationsConfigParameter can be {@literal null}.
117215 * @since 1.8
@@ -120,8 +218,13 @@ public void setKeyspaceNotificationsConfigParameter(String keyspaceNotifications
120218 this .keyspaceNotificationsConfigParameter = keyspaceNotificationsConfigParameter ;
121219 }
122220
123- @ Override
124- public void afterPropertiesSet () throws Exception {
125- init ();
221+ /**
222+ * Get the configured {@link String setting} for {@literal notify-keyspace-events}.
223+ *
224+ * @return the configured {@link String setting} for {@literal notify-keyspace-events}.
225+ */
226+ @ Nullable
227+ protected String getKeyspaceNotificationsConfigParameter () {
228+ return this .keyspaceNotificationsConfigParameter ;
126229 }
127230}
0 commit comments