diff --git a/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java b/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java
index 481d7463f..8d2c5cf4a 100644
--- a/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java
+++ b/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java
@@ -21,12 +21,16 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
@@ -50,13 +54,14 @@ import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.util.ByteUtils;
import org.springframework.data.util.CloseableIterator;
import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
/**
* Redis specific {@link KeyValueAdapter} implementation. Uses binary codec to read/write data from/to Redis. Objects
* are stored in a Redis Hash using the value of {@link RedisHash}, the {@link KeyspaceConfiguration} or just
* {@link Class#getName()} as a prefix.
* Example
- *
+ *
*
*
* @RedisHash("persons")
@@ -64,8 +69,8 @@ import org.springframework.util.Assert;
* @Id String id;
* String name;
* }
- *
- *
+ *
+ *
* prefix ID
* | |
* V V
@@ -76,29 +81,33 @@ import org.springframework.util.Assert;
* 4) Rand al'Thor
*
*
- *
+ *
*
* The {@link KeyValueAdapter} is not intended to store simple types such as {@link String} values.
* Please use {@link RedisTemplate} for this purpose.
- *
+ *
* @author Christoph Strobl
* @author Mark Paluch
* @since 1.7
*/
public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
- implements ApplicationContextAware, ApplicationListener {
+ implements InitializingBean, ApplicationContextAware, ApplicationListener {
private static final Logger LOGGER = LoggerFactory.getLogger(RedisKeyValueAdapter.class);
private RedisOperations, ?> redisOps;
private RedisConverter converter;
private RedisMessageListenerContainer messageListenerContainer;
- private KeyExpirationEventMessageListener expirationListener;
+ private AtomicReference expirationListener = new AtomicReference(
+ null);
+ private ApplicationEventPublisher eventPublisher;
+
+ private EnableKeyspaceEvents enableKeyspaceEvents = EnableKeyspaceEvents.ON_STARTUP;
/**
* Creates new {@link RedisKeyValueAdapter} with default {@link RedisMappingContext} and default
* {@link CustomConversions}.
- *
+ *
* @param redisOps must not be {@literal null}.
*/
public RedisKeyValueAdapter(RedisOperations, ?> redisOps) {
@@ -107,7 +116,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
/**
* Creates new {@link RedisKeyValueAdapter} with default {@link CustomConversions}.
- *
+ *
* @param redisOps must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
*/
@@ -117,7 +126,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
/**
* Creates new {@link RedisKeyValueAdapter}.
- *
+ *
* @param redisOps must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
* @param customConversions can be {@literal null}.
@@ -138,12 +147,12 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
converter = mappingConverter;
this.redisOps = redisOps;
- initKeyExpirationListener();
+ intiMessageListenerContainer();
}
/**
* Creates new {@link RedisKeyValueAdapter} with specific {@link RedisConverter}.
- *
+ *
* @param redisOps must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
*/
@@ -156,7 +165,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
converter = redisConverter;
this.redisOps = redisOps;
- initKeyExpirationListener();
+ intiMessageListenerContainer();
}
/**
@@ -175,6 +184,14 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
converter.write(item, rdo);
}
+ if (ObjectUtils.nullSafeEquals(EnableKeyspaceEvents.ON_DEMAND, enableKeyspaceEvents)
+ && this.expirationListener.get() == null) {
+
+ if (rdo.getTimeToLive() != null && rdo.getTimeToLive().longValue() > 0) {
+ initKeyExpirationListener();
+ }
+ }
+
if (rdo.getId() == null) {
rdo.setId(converter.getConversionService().convert(id, String.class));
@@ -397,7 +414,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
/**
* Execute {@link RedisCallback} via underlying {@link RedisOperations}.
- *
+ *
* @param callback must not be {@literal null}.
* @see RedisOperations#execute(RedisCallback)
* @return
@@ -408,7 +425,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
/**
* Get the {@link RedisConverter} in use.
- *
+ *
* @return never {@literal null}.
*/
public RedisConverter getConverter() {
@@ -430,7 +447,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
/**
* Convert given source to binary representation using the underlying {@link ConversionService}.
- *
+ *
* @param source
* @return
* @throws ConverterNotFoundException
@@ -444,13 +461,38 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
return converter.getConversionService().convert(source, byte[].class);
}
+ /**
+ * Configure usage of {@link KeyExpirationEventMessageListener}.
+ *
+ * @param enableKeyspaceEvents
+ * @since 1.8
+ */
+ public void setEnableKeyspaceEvents(EnableKeyspaceEvents enableKeyspaceEvents) {
+ this.enableKeyspaceEvents = enableKeyspaceEvents;
+ }
+
+ /**
+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+ * @since 1.8
+ */
+ @Override
+ public void afterPropertiesSet() {
+
+ if (ObjectUtils.nullSafeEquals(EnableKeyspaceEvents.ON_STARTUP, this.enableKeyspaceEvents)) {
+ initKeyExpirationListener();
+ }
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
public void destroy() throws Exception {
- this.expirationListener.destroy();
+ if (this.expirationListener.get() != null) {
+ this.expirationListener.get().destroy();
+ }
+
this.messageListenerContainer.destroy();
}
@@ -490,26 +532,39 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.expirationListener.setApplicationEventPublisher(applicationContext);
+ this.eventPublisher = applicationContext;
}
- private void initKeyExpirationListener() {
+ private void intiMessageListenerContainer() {
this.messageListenerContainer = new RedisMessageListenerContainer();
messageListenerContainer.setConnectionFactory(((RedisTemplate, ?>) redisOps).getConnectionFactory());
messageListenerContainer.afterPropertiesSet();
messageListenerContainer.start();
+ }
- this.expirationListener = new MappingExpirationListener(this.messageListenerContainer, this.redisOps,
- this.converter);
- this.expirationListener.init();
+ private void initKeyExpirationListener() {
+
+ if (this.expirationListener.get() == null) {
+
+ MappingExpirationListener listener = new MappingExpirationListener(this.messageListenerContainer, this.redisOps,
+ this.converter);
+
+ if (this.eventPublisher != null) {
+ listener.setApplicationEventPublisher(this.eventPublisher);
+ }
+
+ if (this.expirationListener.compareAndSet(null, listener)) {
+ listener.init();
+ }
+ }
}
/**
* {@link MessageListener} implementation used to capture Redis keypspace notifications. Tries to read a previously
* created phantom key {@code keyspace:id:phantom} to provide the expired object as part of the published
* {@link RedisKeyExpiredEvent}.
- *
+ *
* @author Christoph Strobl
* @since 1.7
*/
@@ -520,7 +575,7 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
/**
* Creates new {@link MappingExpirationListener}.
- *
+ *
* @param listenerContainer
* @param ops
* @param converter
@@ -582,4 +637,26 @@ public class RedisKeyValueAdapter extends AbstractKeyValueAdapter
}
}
+ /**
+ * @author Christoph Strobl
+ * @since 1.8
+ */
+ public static enum EnableKeyspaceEvents {
+
+ /**
+ * Initializes the {@link KeyExpirationEventMessageListener} on startup.
+ */
+ ON_STARTUP,
+
+ /**
+ * Initializes the {@link KeyExpirationEventMessageListener} on first insert having expiration time set.
+ */
+ ON_DEMAND,
+
+ /**
+ * Turn {@link KeyExpirationEventMessageListener} usage off. No expiration events will be received.
+ */
+ OFF
+ }
+
}
diff --git a/src/main/java/org/springframework/data/redis/repository/configuration/EnableRedisRepositories.java b/src/main/java/org/springframework/data/redis/repository/configuration/EnableRedisRepositories.java
index 329a3d652..128048c43 100644
--- a/src/main/java/org/springframework/data/redis/repository/configuration/EnableRedisRepositories.java
+++ b/src/main/java/org/springframework/data/redis/repository/configuration/EnableRedisRepositories.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 the original author or authors.
+ * Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,9 +27,11 @@ import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.data.keyvalue.core.KeyValueOperations;
import org.springframework.data.keyvalue.repository.config.QueryCreatorType;
+import org.springframework.data.redis.core.RedisKeyValueAdapter.EnableKeyspaceEvents;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
import org.springframework.data.redis.core.index.IndexConfiguration;
+import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.repository.query.RedisQueryCreator;
import org.springframework.data.redis.repository.support.RedisRepositoryFactoryBean;
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
@@ -53,7 +55,8 @@ public @interface EnableRedisRepositories {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
- * {@code @EnableRedisRepositories("org.my.pkg")} instead of {@code @EnableRedisRepositories(basePackages="org.my.pkg")}.
+ * {@code @EnableRedisRepositories("org.my.pkg")} instead of
+ * {@code @EnableRedisRepositories(basePackages="org.my.pkg")}.
*/
String[] value() default {};
@@ -154,4 +157,12 @@ public @interface EnableRedisRepositories {
*/
Class extends KeyspaceConfiguration> keyspaceConfiguration() default KeyspaceConfiguration.class;
+ /**
+ * Configure usage of {@link KeyExpirationEventMessageListener}.
+ *
+ * @return
+ * @since 1.8
+ */
+ EnableKeyspaceEvents enableKeyspaceEvents() default EnableKeyspaceEvents.ON_DEMAND;
+
}
diff --git a/src/main/java/org/springframework/data/redis/repository/configuration/RedisRepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/redis/repository/configuration/RedisRepositoryConfigurationExtension.java
index fd34a64f1..f6ed24137 100644
--- a/src/main/java/org/springframework/data/redis/repository/configuration/RedisRepositoryConfigurationExtension.java
+++ b/src/main/java/org/springframework/data/redis/repository/configuration/RedisRepositoryConfigurationExtension.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 the original author or authors.
+ * Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -122,6 +122,11 @@ public class RedisRepositoryConfigurationExtension extends KeyValueRepositoryCon
new RuntimeBeanReference(REDIS_CONVERTER_BEAN_NAME));
redisKeyValueAdapterDefinition.setConstructorArgumentValues(constructorArgumentValuesForRedisKeyValueAdapter);
+
+ DirectFieldAccessor dfa = new DirectFieldAccessor(configurationSource);
+ AnnotationAttributes aa = (AnnotationAttributes) dfa.getPropertyValue("attributes");
+ redisKeyValueAdapterDefinition.setAttribute("enableKeyspaceEvents", aa.getEnum("enableKeyspaceEvents"));
+
registerIfNotAlreadyRegistered(redisKeyValueAdapterDefinition, registry, REDIS_ADAPTER_BEAN_NAME,
configurationSource);
diff --git a/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterTests.java b/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterTests.java
index 8084ee8d5..4c580c2c2 100644
--- a/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterTests.java
+++ b/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterTests.java
@@ -69,6 +69,7 @@ public class RedisKeyValueAdapterTests {
mappingContext.afterPropertiesSet();
adapter = new RedisKeyValueAdapter(template, mappingContext);
+ adapter.afterPropertiesSet();
template.execute(new RedisCallback() {
diff --git a/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterUnitTests.java b/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterUnitTests.java
index 6783ed65e..19e13e3c0 100644
--- a/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterUnitTests.java
+++ b/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterUnitTests.java
@@ -16,51 +16,73 @@
package org.springframework.data.redis.core;
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
+import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
+import static org.springframework.test.util.ReflectionTestUtils.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.data.annotation.Id;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
+import org.springframework.data.redis.core.RedisKeyValueAdapter.EnableKeyspaceEvents;
import org.springframework.data.redis.core.convert.Bucket;
+import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
+import org.springframework.data.redis.core.convert.MappingConfiguration;
import org.springframework.data.redis.core.convert.RedisData;
import org.springframework.data.redis.core.convert.SimpleIndexedPropertyValue;
+import org.springframework.data.redis.core.index.IndexConfiguration;
+import org.springframework.data.redis.core.mapping.RedisMappingContext;
+import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
/**
* Unit tests for {@link RedisKeyValueAdapter}.
*
- * @author Mark Paluch
* @author Christoph Strobl
+ * @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class RedisKeyValueAdapterUnitTests {
- RedisTemplate, ?> redisTemplate;
- RedisKeyValueAdapter redisKeyValueAdapter;
-
+ RedisKeyValueAdapter adapter;
+ RedisTemplate, ?> template;
+ RedisMappingContext context;
@Mock JedisConnectionFactory jedisConnectionFactoryMock;
@Mock RedisConnection redisConnectionMock;
@Before
public void setUp() throws Exception {
- redisTemplate = new RedisTemplate