Skip to content

Commit 738d37b

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 e9bb9e0 commit 738d37b

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
@@ -112,6 +112,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
112112
private RedisOperations<?, ?> redisOps;
113113
private RedisConverter converter;
114114
private @Nullable RedisMessageListenerContainer messageListenerContainer;
115+
private boolean managedListenerContainer = true;
115116
private final AtomicReference<KeyExpirationEventMessageListener> expirationListener = new AtomicReference<>(null);
116117
private @Nullable ApplicationEventPublisher eventPublisher;
117118

@@ -179,7 +180,6 @@ public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps, RedisConverter redis
179180

180181
this.converter = redisConverter;
181182
this.redisOps = redisOps;
182-
initMessageListenerContainer();
183183
}
184184

185185
/**
@@ -216,7 +216,7 @@ public Object put(Object id, Object item, String keyspace) {
216216

217217
connection.hMSet(objectKey, rdo.getBucket().rawMap());
218218

219-
if(isNew) {
219+
if (isNew) {
220220
connection.sAdd(toBytes(rdo.getKeyspace()), key);
221221
}
222222

@@ -311,7 +311,7 @@ public <T> T delete(Object id, String keyspace, Class<T> type) {
311311
connection.sRem(binKeyspace, binId);
312312
new IndexWriter(connection, converter).removeKeyFromIndexes(asString(keyspace), binId);
313313

314-
if(RedisKeyValueAdapter.this.keepShadowCopy()) {
314+
if (RedisKeyValueAdapter.this.keepShadowCopy()) {
315315

316316
RedisPersistentEntity<?> persistentEntity = converter.getMappingContext().getPersistentEntity(type);
317317
if (persistentEntity != null && persistentEntity.isExpiring()) {
@@ -464,7 +464,7 @@ public void update(PartialUpdate<?> update) {
464464

465465
connection.persist(redisKey);
466466

467-
if(keepShadowCopy()) {
467+
if (keepShadowCopy()) {
468468
connection.del(ByteUtils.concat(redisKey, BinaryKeyspaceIdentifier.PHANTOM_SUFFIX));
469469
}
470470
}
@@ -625,7 +625,6 @@ private <T> T readBackTimeToLiveIfSet(@Nullable byte[] key, @Nullable T target)
625625

626626
/**
627627
* @return {@literal true} if {@link RedisData#getTimeToLive()} has a positive value.
628-
*
629628
* @param data must not be {@literal null}.
630629
* @since 2.3.7
631630
*/
@@ -643,6 +642,28 @@ public void setEnableKeyspaceEvents(EnableKeyspaceEvents enableKeyspaceEvents) {
643642
this.enableKeyspaceEvents = enableKeyspaceEvents;
644643
}
645644

645+
/**
646+
* Configure a {@link RedisMessageListenerContainer} to listen for Keyspace expiry events. The container can only be
647+
* set when this bean hasn't been yet {@link #afterPropertiesSet() initialized}.
648+
*
649+
* @param messageListenerContainer the container to use.
650+
* @since 2.7.2
651+
* @throws IllegalStateException when trying to set a {@link RedisMessageListenerContainer} after
652+
* {@link #afterPropertiesSet()} has been called to initialize a managed container instance.
653+
*/
654+
public void setMessageListenerContainer(RedisMessageListenerContainer messageListenerContainer) {
655+
656+
Assert.notNull(messageListenerContainer, "RedisMessageListenerContainer must not be null");
657+
658+
if (this.managedListenerContainer && this.messageListenerContainer != null) {
659+
throw new IllegalStateException(
660+
"Cannot set RedisMessageListenerContainer after initializing a managed RedisMessageListenerContainer instance");
661+
}
662+
663+
this.managedListenerContainer = false;
664+
this.messageListenerContainer = messageListenerContainer;
665+
}
666+
646667
/**
647668
* Configure the {@literal notify-keyspace-events} property if not already set. Use an empty {@link String} or
648669
* {@literal null} to retain existing server settings.
@@ -671,6 +692,10 @@ public void setShadowCopy(ShadowCopy shadowCopy) {
671692
@Override
672693
public void afterPropertiesSet() {
673694

695+
if (this.managedListenerContainer) {
696+
initMessageListenerContainer();
697+
}
698+
674699
if (ObjectUtils.nullSafeEquals(EnableKeyspaceEvents.ON_STARTUP, this.enableKeyspaceEvents)) {
675700
initKeyExpirationListener();
676701
}
@@ -682,8 +707,9 @@ public void destroy() throws Exception {
682707
this.expirationListener.get().destroy();
683708
}
684709

685-
if (this.messageListenerContainer != null) {
710+
if (this.managedListenerContainer && this.messageListenerContainer != null) {
686711
this.messageListenerContainer.destroy();
712+
this.messageListenerContainer = null;
687713
}
688714
}
689715

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.7.2
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
@@ -138,15 +138,19 @@ protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
138138

139139
private static AbstractBeanDefinition createRedisKeyValueAdapter(RepositoryConfigurationSource configuration) {
140140

141-
return BeanDefinitionBuilder.rootBeanDefinition(RedisKeyValueAdapter.class) //
141+
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(RedisKeyValueAdapter.class) //
142142
.addConstructorArgReference(configuration.getRequiredAttribute("redisTemplateRef", String.class)) //
143143
.addConstructorArgReference(REDIS_CONVERTER_BEAN_NAME) //
144144
.addPropertyValue("enableKeyspaceEvents",
145145
configuration.getRequiredAttribute("enableKeyspaceEvents", EnableKeyspaceEvents.class)) //
146146
.addPropertyValue("keyspaceNotificationsConfigParameter",
147147
configuration.getAttribute("keyspaceNotificationsConfigParameter", String.class).orElse("")) //
148-
.addPropertyValue("shadowCopy", configuration.getRequiredAttribute("shadowCopy", ShadowCopy.class)) //
149-
.getBeanDefinition();
148+
.addPropertyValue("shadowCopy", configuration.getRequiredAttribute("shadowCopy", ShadowCopy.class));
149+
150+
configuration.getAttribute("messageListenerContainerRef")
151+
.ifPresent(it -> builder.addPropertyReference("messageListenerContainer", it));
152+
153+
return builder.getBeanDefinition();
150154
}
151155

152156
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)