Custom task executors for RedisMessageListenerContainer
Tasks executors used by redisMessageListenerContainer can now optionally be overriden with beans named springSessionRedisTaskExecutor and/or springSessionRedisSubscriptionExecutor. Fixes gh-314
This commit is contained in:
committed by
Rob Winch
parent
d209a10514
commit
5bf12fdd93
@@ -574,6 +574,12 @@ A value of `RedisFlushMode.IMMEDIATE` will write to Redis as soon as possible.
|
||||
|
||||
You can customize the serialization by creating a Bean named `springSessionDefaultRedisSerializer` that implements `RedisSerializer<Object>`.
|
||||
|
||||
==== Redis TaskExecutor
|
||||
|
||||
`RedisOperationsSessionRepository` is subscribed to receive events from redis using a `RedisMessageListenerContainer`.
|
||||
You can customize the way those events are dispatched, by creating a Bean named `springSessionRedisTaskExecutor` and/or a Bean `springSessionRedisSubscriptionExecutor`.
|
||||
More details on configuring redis task executors can be found http://docs.spring.io/spring-data-redis/docs/current/reference/html/#redis:pubsub:subscribe:containers[here].
|
||||
|
||||
[[api-redisoperationssessionrepository-storage]]
|
||||
==== Storage Details
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.springframework.session.data.redis.taskexecutor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.BoundSetOperations;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Vladimir Tsanev
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class RedisListenerContainerTaskExecutorITests {
|
||||
|
||||
@Autowired
|
||||
SessionTaskExecutor executor;
|
||||
|
||||
@Autowired
|
||||
RedisOperations<Object, Object> redis;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
executor.setLock(lock);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRedisDelEventsAreDispatchedInSessionTaskExecutor() throws InterruptedException {
|
||||
BoundSetOperations<Object, Object> ops =
|
||||
redis.boundSetOps("spring:session:RedisListenerContainerTaskExecutorITests:expirations:dummy");
|
||||
ops.add("value");
|
||||
ops.remove("value");
|
||||
synchronized (lock) {
|
||||
lock.wait(TimeUnit.SECONDS.toMillis(1));
|
||||
}
|
||||
assertThat(executor.taskDispatched()).isTrue();
|
||||
|
||||
}
|
||||
|
||||
static class SessionTaskExecutor implements TaskExecutor {
|
||||
private Object lock;
|
||||
private final Executor executor;
|
||||
|
||||
private boolean taskDispatched;
|
||||
|
||||
public SessionTaskExecutor(Executor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public void setLock(Object lock) {
|
||||
this.lock = lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task) {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
executor.execute(task);
|
||||
} finally {
|
||||
taskDispatched = true;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean taskDispatched() {
|
||||
return taskDispatched;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests")
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
JedisConnectionFactory connectionFactory() throws Exception {
|
||||
JedisConnectionFactory factory = new JedisConnectionFactory();
|
||||
factory.setUsePool(false);
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
Executor springSessionRedisTaskExecutor() {
|
||||
return new SessionTaskExecutor(Executors.newSingleThreadExecutor());
|
||||
}
|
||||
|
||||
@Bean
|
||||
Executor springSessionRedisSubscriptionExecutor() {
|
||||
return new SimpleAsyncTaskExecutor();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,7 +363,9 @@ public class RedisOperationsSessionRepository implements FindByIndexNameSessionR
|
||||
}
|
||||
|
||||
/**
|
||||
* @param redisFlushMode
|
||||
* Sets the redis flush mode. Default flush mode is {@link RedisFlushMode#ON_SAVE}.
|
||||
*
|
||||
* @param redisFlushMode the new redis flush mode
|
||||
*/
|
||||
public void setRedisFlushMode(RedisFlushMode redisFlushMode) {
|
||||
Assert.notNull(redisFlushMode, "redisFlushMode cannot be null");
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.session.data.redis.config.annotation.web.http;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -69,12 +70,22 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
|
||||
private RedisSerializer<Object> defaultRedisSerializer;
|
||||
|
||||
private Executor redisTaskExecutor;
|
||||
|
||||
private Executor redisSubscriptionExecutor;
|
||||
|
||||
@Bean
|
||||
public RedisMessageListenerContainer redisMessageListenerContainer(
|
||||
RedisConnectionFactory connectionFactory, RedisOperationsSessionRepository messageListener) {
|
||||
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
if (redisTaskExecutor != null) {
|
||||
container.setTaskExecutor(redisTaskExecutor);
|
||||
}
|
||||
if (redisSubscriptionExecutor != null) {
|
||||
container.setSubscriptionExecutor(redisSubscriptionExecutor);
|
||||
}
|
||||
container.addMessageListener(messageListener,
|
||||
Arrays.asList(new PatternTopic("__keyevent@*:del"), new PatternTopic("__keyevent@*:expired")));
|
||||
container.addMessageListener(messageListener, Arrays.asList(new PatternTopic(messageListener.getSessionCreatedChannelPrefix() + "*")));
|
||||
@@ -181,4 +192,16 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
public void setDefaultRedisSerializer(RedisSerializer<Object> defaultRedisSerializer) {
|
||||
this.defaultRedisSerializer = defaultRedisSerializer;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
@Qualifier("springSessionRedisTaskExecutor")
|
||||
public void setRedisTaskExecutor(Executor redisTaskExecutor) {
|
||||
this.redisTaskExecutor = redisTaskExecutor;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
@Qualifier("springSessionRedisSubscriptionExecutor")
|
||||
public void setRedisSubscriptionExecutor(Executor redisSubscriptionExecutor) {
|
||||
this.redisSubscriptionExecutor = redisSubscriptionExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.session.data.redis.config.annotation.web.http;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.scheduling.SchedulingAwareRunnable;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
/**
|
||||
* @author Vladimir Tsanev
|
||||
*
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class RedisHttpSessionConfigurationOverrideSessionTaskExecutor {
|
||||
|
||||
@Autowired
|
||||
RedisMessageListenerContainer redisMessageListenerContainer;
|
||||
|
||||
@Autowired
|
||||
Executor springSessionRedisTaskExecutor;
|
||||
|
||||
@Test
|
||||
public void overrideSessionTaskExecutor() {
|
||||
verify(springSessionRedisTaskExecutor, times(1)).execute(any(SchedulingAwareRunnable.class));
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession
|
||||
@Configuration
|
||||
static class Config {
|
||||
@Bean
|
||||
public Executor springSessionRedisTaskExecutor() {
|
||||
return mock(Executor.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisConnectionFactory connectionFactory() {
|
||||
RedisConnectionFactory factory = mock(RedisConnectionFactory.class);
|
||||
RedisConnection connection = mock(RedisConnection.class);
|
||||
when(factory.getConnection()).thenReturn(connection);
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.session.data.redis.config.annotation.web.http;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.scheduling.SchedulingAwareRunnable;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Vladimir Tsanev
|
||||
*
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class RedisHttpSessionConfigurationOverrideSessionTaskExecutors {
|
||||
|
||||
@Autowired
|
||||
RedisMessageListenerContainer redisMessageListenerContainer;
|
||||
|
||||
@Autowired
|
||||
Executor springSessionRedisTaskExecutor;
|
||||
|
||||
@Autowired
|
||||
Executor springSessionRedisSubscriptionExecutor;
|
||||
|
||||
@Test
|
||||
public void overrideSessionTaskExecutors() {
|
||||
verify(springSessionRedisSubscriptionExecutor, times(1)).execute(any(SchedulingAwareRunnable.class));
|
||||
verify(springSessionRedisTaskExecutor, never()).execute(any(Runnable.class));
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession
|
||||
@Configuration
|
||||
static class Config {
|
||||
@Bean
|
||||
public Executor springSessionRedisTaskExecutor() {
|
||||
return mock(Executor.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Executor springSessionRedisSubscriptionExecutor() {
|
||||
return mock(Executor.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisConnectionFactory connectionFactory() {
|
||||
RedisConnectionFactory factory = mock(RedisConnectionFactory.class);
|
||||
RedisConnection connection = mock(RedisConnection.class);
|
||||
when(factory.getConnection()).thenReturn(connection);
|
||||
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user