Skip to content

Commit bfc1fa5

Browse files
committed
Change default value for KeyspaceEventMessageListener keyspace event notifications.
Users must now explicitly call setKeyspaceNotificationsConfigParameter(:String) to a valid redis.config, notify-keyspace-events value to enable Redis keyspace notifications. This aligns with the Redis servers default setting for notify-keyspace-events in redis.conf, which is disabled by default. Additionally, the default value for KeyExpirationEventMessageListener has been changed to 'Ex', for development-time convenience only. However, users should be aware that any notify-keyspace-events configuration only applies once on Spring container initialization and any Redis server reboot will not remember dynamic configuration modifications applied at runtime. Therefore, it is recommended that users applly infrastructure-related configuration changes directly to redis.conf. Closes #2670
1 parent 2955ff3 commit bfc1fa5

File tree

4 files changed

+406
-39
lines changed

4 files changed

+406
-39
lines changed

src/main/java/org/springframework/data/redis/listener/KeyExpirationEventMessageListener.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,36 @@
2525
/**
2626
* {@link MessageListener} publishing {@link RedisKeyExpiredEvent}s via {@link ApplicationEventPublisher} by listening
2727
* to Redis keyspace notifications for key expirations.
28+
* <p>
29+
* For development-time convenience the {@link #setKeyspaceNotificationsConfigParameter(String)} is set to
30+
* {@literal "Ex"}, by default. However, it is strongly recommended that users specifically set
31+
* {@literal notify-keyspace-events} to the appropriate value on the Redis server, in {@literal redis.conf}.
32+
* <p>
33+
* Any Redis server configuration coming from your Spring (Data Redis) application only occurs during Spring container
34+
* initialization, and is not persisted across Redis server restarts.
2835
*
2936
* @author Christoph Strobl
37+
* @author John Blum
3038
* @since 1.7
3139
*/
3240
public class KeyExpirationEventMessageListener extends KeyspaceEventMessageListener implements
3341
ApplicationEventPublisherAware {
3442

43+
private static final String EXPIRED_KEY_EVENTS = "Ex";
44+
3545
private static final Topic KEYEVENT_EXPIRED_TOPIC = new PatternTopic("__keyevent@*__:expired");
3646

3747
private @Nullable ApplicationEventPublisher publisher;
3848

3949
/**
40-
* Creates new {@link MessageListener} for {@code __keyevent@*__:expired} messages.
50+
* Creates new {@link MessageListener} for {@code __keyevent@*__:expired} messages and configures notification on
51+
* expired keys ({@literal Ex}).
4152
*
4253
* @param listenerContainer must not be {@literal null}.
4354
*/
4455
public KeyExpirationEventMessageListener(RedisMessageListenerContainer listenerContainer) {
4556
super(listenerContainer);
57+
setKeyspaceNotificationsConfigParameter(EXPIRED_KEY_EVENTS);
4658
}
4759

4860
@Override

src/main/java/org/springframework/data/redis/listener/KeyspaceEventMessageListener.java

+122-35
Original file line numberDiff line numberDiff line change
@@ -17,101 +17,183 @@
1717

1818
import java.util.Properties;
1919

20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
2022
import org.springframework.beans.factory.DisposableBean;
2123
import org.springframework.beans.factory.InitializingBean;
2224
import org.springframework.data.redis.connection.Message;
2325
import org.springframework.data.redis.connection.MessageListener;
2426
import org.springframework.data.redis.connection.RedisConnection;
27+
import org.springframework.data.redis.connection.RedisConnectionFactory;
2528
import org.springframework.lang.Nullable;
2629
import org.springframework.util.Assert;
2730
import org.springframework.util.ObjectUtils;
2831
import 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
*/
3755
public abstract class KeyspaceEventMessageListener implements MessageListener, InitializingBean, DisposableBean {
3856

57+
static final String NOTIFY_KEYSPACE_EVENTS = "notify-keyspace-events";
58+
3959
private static final Topic TOPIC_ALL_KEYEVENTS = new PatternTopic("__keyevent@*");
4060

41-
private final RedisMessageListenerContainer listenerContainer;
61+
private final Logger logger = LoggerFactory.getLogger(getClass());
62+
63+
private final RedisMessageListenerContainer messageListenerContainer;
4264

43-
private String keyspaceNotificationsConfigParameter = "EA";
65+
private String keyspaceNotificationsConfigParameter = "";
4466

4567
/**
46-
* Creates new {@link KeyspaceEventMessageListener}.
68+
* Creates a new {@link KeyspaceEventMessageListener}.
4769
*
48-
* @param listenerContainer must not be {@literal null}.
70+
* @param messageListenerContainer {@link RedisMessageListenerContainer} in which this listener will be registered;
71+
* must not be {@literal null}.
4972
*/
50-
public KeyspaceEventMessageListener(RedisMessageListenerContainer listenerContainer) {
73+
public KeyspaceEventMessageListener(RedisMessageListenerContainer messageListenerContainer) {
5174

52-
Assert.notNull(listenerContainer, "RedisMessageListenerContainer to run in must not be null");
53-
this.listenerContainer = listenerContainer;
75+
Assert.notNull(messageListenerContainer, "RedisMessageListenerContainer to run in must not be null");
76+
77+
this.messageListenerContainer = messageListenerContainer;
78+
}
79+
80+
/**
81+
* Returns a reference to the configured {@link Logger}.
82+
*
83+
* @return a reference to the configured {@link Logger}.
84+
*/
85+
protected Logger getLogger() {
86+
return this.logger;
87+
}
88+
89+
/**
90+
* Returns a configured reference to the {@link RedisMessageListenerContainer} to which this {@link MessageListener}
91+
* is registered.
92+
*
93+
* @return a configured reference to the {@link RedisMessageListenerContainer} to which this {@link MessageListener}
94+
* is registered.
95+
*/
96+
protected RedisMessageListenerContainer getMessageListenerContainer() {
97+
return this.messageListenerContainer;
5498
}
5599

56100
@Override
57101
public void onMessage(Message message, @Nullable byte[] pattern) {
58102

59-
if (ObjectUtils.isEmpty(message.getChannel()) || ObjectUtils.isEmpty(message.getBody())) {
60-
return;
103+
if (containsChannelContent(message)) {
104+
doHandleMessage(message);
61105
}
106+
}
62107

63-
doHandleMessage(message);
108+
// Message must have a channel and body (contain content)
109+
private boolean containsChannelContent(Message message) {
110+
return !(ObjectUtils.isEmpty(message.getChannel()) || ObjectUtils.isEmpty(message.getBody()));
64111
}
65112

66113
/**
67-
* Handle the actual message
114+
* Handle the actual {@link Message}.
68115
*
69-
* @param message never {@literal null}.
116+
* @param message {@link Message} to process; never {@literal null}.
70117
*/
71118
protected abstract void doHandleMessage(Message message);
72119

120+
@Override
121+
public void afterPropertiesSet() throws Exception {
122+
init();
123+
}
124+
73125
/**
74-
* Initialize the message listener by writing requried redis config for {@literal notify-keyspace-events} and
75-
* registering the listener within the container.
126+
* Initialize this {@link MessageListener} by writing required Redis server config
127+
* for {@literal notify-keyspace-events} and registering this {@link MessageListener}
128+
* with the {@link RedisMessageListenerContainer}.
76129
*/
77130
public void init() {
78131

79-
if (StringUtils.hasText(keyspaceNotificationsConfigParameter)) {
132+
String keyspaceNotificationsConfigParameter = getKeyspaceNotificationsConfigParameter();
80133

81-
RedisConnection connection = listenerContainer.getConnectionFactory().getConnection();
134+
if (isSet(keyspaceNotificationsConfigParameter)) {
135+
configureKeyspaceEventNotifications(keyspaceNotificationsConfigParameter);
136+
}
137+
138+
doRegister(getMessageListenerContainer());
139+
}
82140

83-
try {
141+
private boolean isSet(@Nullable String value) {
142+
return StringUtils.hasText(value);
143+
}
84144

85-
Properties config = connection.getConfig("notify-keyspace-events");
145+
void configureKeyspaceEventNotifications(String keyspaceNotificationsConfigParameter) {
86146

87-
if (!StringUtils.hasText(config.getProperty("notify-keyspace-events"))) {
88-
connection.setConfig("notify-keyspace-events", keyspaceNotificationsConfigParameter);
89-
}
147+
RedisConnectionFactory connectionFactory = getMessageListenerContainer().getConnectionFactory();
90148

91-
} finally {
92-
connection.close();
149+
if (connectionFactory != null) {
150+
try (RedisConnection connection = connectionFactory.getConnection()) {
151+
if (canChangeNotifyKeyspaceEvents(connection)) {
152+
setKeyspaceEventNotifications(connection, keyspaceNotificationsConfigParameter);
153+
}
93154
}
94155
}
156+
else {
157+
if (getLogger().isWarnEnabled()) {
158+
getLogger().warn("Unable to configure notification on keyspace events;"
159+
+ " no RedisConnectionFactory was configured in the RedisMessageListenerContainer");
160+
}
161+
}
162+
}
163+
164+
private boolean canChangeNotifyKeyspaceEvents(@Nullable RedisConnection connection) {
165+
166+
if (connection != null) {
167+
168+
Properties config = connection.serverCommands().getConfig(NOTIFY_KEYSPACE_EVENTS);
169+
170+
return config == null || !isSet(config.getProperty(NOTIFY_KEYSPACE_EVENTS));
171+
}
172+
173+
return false;
174+
}
175+
176+
void setKeyspaceEventNotifications(RedisConnection connection, String keyspaceNotificationsConfigParameter) {
177+
connection.serverCommands().setConfig(NOTIFY_KEYSPACE_EVENTS, keyspaceNotificationsConfigParameter);
178+
}
95179

96-
doRegister(listenerContainer);
180+
@Override
181+
public void destroy() throws Exception {
182+
getMessageListenerContainer().removeMessageListener(this);
97183
}
98184

99185
/**
100-
* Register instance within the container.
186+
* Register instance within the {@link RedisMessageListenerContainer}.
101187
*
102188
* @param container never {@literal null}.
103189
*/
104190
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);
191+
container.addMessageListener(this, TOPIC_ALL_KEYEVENTS);
111192
}
112193

113194
/**
114-
* Set the configuration string to use for {@literal notify-keyspace-events}.
195+
* Set the {@link String configuration setting} (for example: {@literal EA}) to use
196+
* for {@literal notify-keyspace-events}.
115197
*
116198
* @param keyspaceNotificationsConfigParameter can be {@literal null}.
117199
* @since 1.8
@@ -120,8 +202,13 @@ public void setKeyspaceNotificationsConfigParameter(String keyspaceNotifications
120202
this.keyspaceNotificationsConfigParameter = keyspaceNotificationsConfigParameter;
121203
}
122204

123-
@Override
124-
public void afterPropertiesSet() throws Exception {
125-
init();
205+
/**
206+
* Get the configured {@link String setting} for {@literal notify-keyspace-events}.
207+
*
208+
* @return the configured {@link String setting} for {@literal notify-keyspace-events}.
209+
*/
210+
@Nullable
211+
protected String getKeyspaceNotificationsConfigParameter() {
212+
return this.keyspaceNotificationsConfigParameter;
126213
}
127214
}

src/test/java/org/springframework/data/redis/listener/KeyExpirationEventMessageListenerIntegrationTests.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,10 @@
2727
import org.junit.jupiter.api.Test;
2828
import org.mockito.ArgumentCaptor;
2929

30-
import org.springframework.beans.factory.DisposableBean;
3130
import org.springframework.context.ApplicationEvent;
3231
import org.springframework.context.ApplicationEventPublisher;
3332
import org.springframework.data.redis.connection.RedisConnection;
3433
import org.springframework.data.redis.connection.RedisConnectionFactory;
35-
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
3634
import org.springframework.data.redis.connection.jedis.extension.JedisConnectionFactoryExtension;
3735
import org.springframework.data.redis.test.extension.RedisStanalone;
3836

@@ -103,7 +101,7 @@ void listenerShouldPublishEventCorrectly() {
103101
@Test // DATAREDIS-425
104102
void listenerShouldNotReactToDeleteEvents() throws InterruptedException {
105103

106-
byte[] key = ("to-delete:" + UUID.randomUUID().toString()).getBytes();
104+
byte[] key = ("to-delete:" + UUID.randomUUID()).getBytes();
107105

108106
try (RedisConnection connection = connectionFactory.getConnection()) {
109107

0 commit comments

Comments
 (0)