Skip to content

Commit 2d58305

Browse files
committed
Allow configuration of RedisMessageListenerContainer through @EnableRedisRepositories.
We now support configuration of a bean reference to RedisMessageListenerContainer that should be used with `RedisKeyValueAdapter` for easier configuration of the listener container. Closes #1827
1 parent 0fc5da1 commit 2d58305

File tree

4 files changed

+87
-9
lines changed

4 files changed

+87
-9
lines changed

src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
113113
private RedisOperations<?, ?> redisOps;
114114
private RedisConverter converter;
115115
private @Nullable RedisMessageListenerContainer messageListenerContainer;
116+
private boolean managedListenerContainer = true;
116117
private final AtomicReference<KeyExpirationEventMessageListener> expirationListener = new AtomicReference<>(null);
117118
private @Nullable ApplicationEventPublisher eventPublisher;
118119

@@ -195,7 +196,6 @@ public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps, RedisConverter redis
195196

196197
this.converter = redisConverter;
197198
this.redisOps = redisOps;
198-
initMessageListenerContainer();
199199
}
200200

201201
/**
@@ -236,7 +236,7 @@ public Object put(Object id, Object item, String keyspace) {
236236

237237
connection.hMSet(objectKey, rdo.getBucket().rawMap());
238238

239-
if(isNew) {
239+
if (isNew) {
240240
connection.sAdd(toBytes(rdo.getKeyspace()), key);
241241
}
242242

@@ -351,7 +351,7 @@ public <T> T delete(Object id, String keyspace, Class<T> type) {
351351
connection.sRem(binKeyspace, binId);
352352
new IndexWriter(connection, converter).removeKeyFromIndexes(asString(keyspace), binId);
353353

354-
if(RedisKeyValueAdapter.this.keepShadowCopy()) {
354+
if (RedisKeyValueAdapter.this.keepShadowCopy()) {
355355

356356
RedisPersistentEntity<?> persistentEntity = converter.getMappingContext().getPersistentEntity(type);
357357
if (persistentEntity != null && persistentEntity.isExpiring()) {
@@ -524,7 +524,7 @@ public void update(PartialUpdate<?> update) {
524524

525525
connection.persist(redisKey);
526526

527-
if(keepShadowCopy()) {
527+
if (keepShadowCopy()) {
528528
connection.del(ByteUtils.concat(redisKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX));
529529
}
530530
}
@@ -685,7 +685,6 @@ private <T> T readBackTimeToLiveIfSet(@Nullable byte[] key, @Nullable T target)
685685

686686
/**
687687
* @return {@literal true} if {@link RedisData#getTimeToLive()} has a positive value.
688-
*
689688
* @param data must not be {@literal null}.
690689
* @since 2.3.7
691690
*/
@@ -703,6 +702,28 @@ public void setEnableKeyspaceEvents(EnableKeyspaceEvents enableKeyspaceEvents) {
703702
this.enableKeyspaceEvents = enableKeyspaceEvents;
704703
}
705704

705+
/**
706+
* Configure a {@link RedisMessageListenerContainer} to listen for Keyspace expiry events. The container can only be
707+
* set when this bean hasn't been yet {@link #afterPropertiesSet() initialized}.
708+
*
709+
* @param messageListenerContainer the container to use.
710+
* @since 2.6.6
711+
* @throws IllegalStateException when trying to set a {@link RedisMessageListenerContainer} after
712+
* {@link #afterPropertiesSet()} has been called to initialize a managed container instance.
713+
*/
714+
public void setMessageListenerContainer(RedisMessageListenerContainer messageListenerContainer) {
715+
716+
Assert.notNull(messageListenerContainer, "RedisMessageListenerContainer must not be null");
717+
718+
if (this.managedListenerContainer && this.messageListenerContainer != null) {
719+
throw new IllegalStateException(
720+
"Cannot set RedisMessageListenerContainer after initializing a managed RedisMessageListenerContainer instance");
721+
}
722+
723+
this.managedListenerContainer = false;
724+
this.messageListenerContainer = messageListenerContainer;
725+
}
726+
706727
/**
707728
* Configure the {@literal notify-keyspace-events} property if not already set. Use an empty {@link String} or
708729
* {@literal null} to retain existing server settings.
@@ -731,6 +752,10 @@ public void setShadowCopy(ShadowCopy shadowCopy) {
731752
@Override
732753
public void afterPropertiesSet() {
733754

755+
if (this.managedListenerContainer) {
756+
initMessageListenerContainer();
757+
}
758+
734759
if (ObjectUtils.nullSafeEquals(EnableKeyspaceEvents.ON_STARTUP, this.enableKeyspaceEvents)) {
735760
initKeyExpirationListener();
736761
}
@@ -746,8 +771,9 @@ public void destroy() throws Exception {
746771
this.expirationListener.get().destroy();
747772
}
748773

749-
if (this.messageListenerContainer != null) {
774+
if (this.managedListenerContainer && this.messageListenerContainer != null) {
750775
this.messageListenerContainer.destroy();
776+
this.messageListenerContainer = null;
751777
}
752778
}
753779

src/main/java/org/springframework/data/redis/repository/configuration/EnableRedisRepositories.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,16 @@
167167
*/
168168
EnableKeyspaceEvents enableKeyspaceEvents() default EnableKeyspaceEvents.OFF;
169169

170+
/**
171+
* Configure the name of the {@link org.springframework.data.redis.listener.RedisMessageListenerContainer} bean to be
172+
* used for keyspace event subscriptions. Defaults to use an anonymous managed instance by
173+
* {@link org.springframework.data.redis.core.RedisKeyValueAdapter}.
174+
*
175+
* @return
176+
* @since 2.6.6
177+
*/
178+
String messageListenerContainerRef() default "";
179+
170180
/**
171181
* Configuration flag controlling storage of phantom keys (shadow copies) of expiring entities to read them later when
172182
* publishing {@link org.springframework.data.redis.core.RedisKeyspaceEvent keyspace events}.

src/main/java/org/springframework/data/redis/repository/configuration/RedisRepositoryConfigurationExtension.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,20 @@ protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
139139

140140
private static AbstractBeanDefinition createRedisKeyValueAdapter(RepositoryConfigurationSource configuration) {
141141

142-
return BeanDefinitionBuilder.rootBeanDefinition(RedisKeyValueAdapter.class) //
142+
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(RedisKeyValueAdapter.class) //
143143
.addConstructorArgReference(configuration.getRequiredAttribute("redisTemplateRef", String.class)) //
144144
.addConstructorArgReference(REDIS_CONVERTER_BEAN_NAME) //
145145
.addPropertyValue("enableKeyspaceEvents",
146146
configuration.getRequiredAttribute("enableKeyspaceEvents", EnableKeyspaceEvents.class)) //
147147
.addPropertyValue("keyspaceNotificationsConfigParameter",
148148
configuration.getAttribute("keyspaceNotificationsConfigParameter", String.class).orElse("")) //
149149
.addPropertyValue("shadowCopy",
150-
configuration.getRequiredAttribute("shadowCopy", ShadowCopy.class)) //
151-
.getBeanDefinition();
150+
configuration.getRequiredAttribute("shadowCopy", ShadowCopy.class));
151+
152+
configuration.getAttribute("messageListenerContainerRef")
153+
.ifPresent(it -> builder.addPropertyReference("messageListenerContainer", it));
154+
155+
return builder.getBeanDefinition();
152156
}
153157

154158
private static AbstractBeanDefinition createRedisReferenceResolverDefinition(String redisTemplateRef) {

src/test/java/org/springframework/data/redis/repository/configuration/RedisRepositoryConfigurationUnitTests.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.junit.jupiter.api.Test;
2222
import org.junit.jupiter.api.extension.ExtendWith;
23+
import org.mockito.Mockito;
2324

2425
import org.springframework.beans.factory.annotation.Autowired;
2526
import org.springframework.context.ApplicationContext;
@@ -32,14 +33,18 @@
3233
import org.springframework.data.redis.core.RedisKeyValueAdapter;
3334
import org.springframework.data.redis.core.RedisTemplate;
3435
import org.springframework.data.redis.core.convert.ReferenceResolver;
36+
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
3537
import org.springframework.data.repository.Repository;
3638
import org.springframework.test.annotation.DirtiesContext;
3739
import org.springframework.test.context.ContextConfiguration;
3840
import org.springframework.test.context.junit.jupiter.SpringExtension;
3941
import org.springframework.test.util.ReflectionTestUtils;
4042

4143
/**
44+
* Unit tests for Redis Repository configuration.
45+
*
4246
* @author Christoph Strobl
47+
* @author Mark Paluch
4348
*/
4449
public class RedisRepositoryConfigurationUnitTests {
4550

@@ -123,6 +128,39 @@ public void shouldRegisterDefaultBeans() {
123128
}
124129
}
125130

131+
@ExtendWith(SpringExtension.class)
132+
@DirtiesContext
133+
@ContextConfiguration(classes = { WithMessageListenerConfigurationUnitTests.Config.class })
134+
public static class WithMessageListenerConfigurationUnitTests {
135+
136+
@EnableRedisRepositories(considerNestedRepositories = true,
137+
includeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = { ".*ContextSampleRepository" }) },
138+
keyspaceNotificationsConfigParameter = "", messageListenerContainerRef = "myContainer")
139+
static class Config {
140+
141+
@Bean
142+
RedisMessageListenerContainer myContainer() {
143+
return mock(RedisMessageListenerContainer.class);
144+
}
145+
146+
@Bean
147+
RedisTemplate<?, ?> redisTemplate() {
148+
return createTemplateMock();
149+
}
150+
}
151+
152+
@Autowired ApplicationContext ctx;
153+
154+
@Test // DATAREDIS-425
155+
public void shouldConfigureMessageListenerContainer() {
156+
157+
RedisKeyValueAdapter adapter = ctx.getBean("redisKeyValueAdapter", RedisKeyValueAdapter.class);
158+
Object messageListenerContainer = ReflectionTestUtils.getField(adapter, "messageListenerContainer");
159+
160+
assertThat(Mockito.mockingDetails(messageListenerContainer).isMock()).isTrue();
161+
}
162+
}
163+
126164
@RedisHash
127165
static class Sample {
128166
String id;

0 commit comments

Comments
 (0)