Use simple Redis repository by default
Closes gh-1711
This commit is contained in:
committed by
Eleftheria Stein-Kousathana
parent
14ecf21c94
commit
8582b9706d
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2021 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -691,7 +691,7 @@ class RedisIndexedSessionRepositoryITests extends AbstractRedisITests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests")
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests", enableIndexingAndEvents = true)
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -26,13 +26,10 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
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.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
import org.springframework.session.data.redis.RedisSessionRepository.RedisSession;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@@ -223,17 +220,9 @@ class RedisSessionRepositoryITests extends AbstractRedisITests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableSpringHttpSession
|
||||
@EnableRedisHttpSession
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
RedisSessionRepository sessionRepository(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return new RedisSessionRepository(redisTemplate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -113,7 +113,7 @@ class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Session> exten
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1)
|
||||
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1, enableIndexingAndEvents = true)
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -101,7 +101,7 @@ class RedisListenerContainerTaskExecutorITests extends AbstractRedisITests {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests")
|
||||
@EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests", enableIndexingAndEvents = true)
|
||||
static class Config extends BaseConfig {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
|
||||
/**
|
||||
@@ -54,7 +55,8 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* More advanced configurations can extend {@link RedisHttpSessionConfiguration} instead.
|
||||
* More advanced configurations can extend {@link RedisHttpSessionConfiguration} or
|
||||
* {@link RedisIndexedHttpSessionConfiguration} instead.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
@@ -64,7 +66,7 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Import(RedisHttpSessionConfiguration.class)
|
||||
@Import(RedisHttpSessionConfigurationSelector.class)
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public @interface EnableRedisHttpSession {
|
||||
|
||||
@@ -113,13 +115,6 @@ public @interface EnableRedisHttpSession {
|
||||
*/
|
||||
FlushMode flushMode() default FlushMode.ON_SAVE;
|
||||
|
||||
/**
|
||||
* The cron expression for expired session cleanup job. By default runs every minute.
|
||||
* @return the session cleanup cron expression
|
||||
* @since 2.0.0
|
||||
*/
|
||||
String cleanupCron() default RedisHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
|
||||
|
||||
/**
|
||||
* Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
|
||||
* only saves changes made to session.
|
||||
@@ -128,4 +123,13 @@ public @interface EnableRedisHttpSession {
|
||||
*/
|
||||
SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
* Indicate whether the {@link SessionRepository} should publish session events and
|
||||
* support fetching sessions by index. If true, a
|
||||
* {@link RedisIndexedSessionRepository} will be used in place of
|
||||
* {@link RedisSessionRepository}. This will result in slower performance.
|
||||
* @return true if indexing and events should be enabled, false otherwise
|
||||
*/
|
||||
boolean enableIndexingAndEvents() default false;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -16,55 +16,35 @@
|
||||
|
||||
package org.springframework.session.data.redis.config.annotation.web.http;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.listener.PatternTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
|
||||
import org.springframework.session.data.redis.config.ConfigureRedisAction;
|
||||
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
|
||||
@@ -83,86 +63,39 @@ import org.springframework.util.StringValueResolver;
|
||||
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
|
||||
|
||||
static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
|
||||
|
||||
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
|
||||
|
||||
private String redisNamespace = RedisIndexedSessionRepository.DEFAULT_NAMESPACE;
|
||||
private String redisNamespace = RedisSessionRepository.DEFAULT_KEY_NAMESPACE;
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private String cleanupCron = DEFAULT_CLEANUP_CRON;
|
||||
|
||||
private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();
|
||||
|
||||
private RedisConnectionFactory redisConnectionFactory;
|
||||
|
||||
private IndexResolver<Session> indexResolver;
|
||||
|
||||
private RedisSerializer<Object> defaultRedisSerializer;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private Executor redisTaskExecutor;
|
||||
|
||||
private Executor redisSubscriptionExecutor;
|
||||
|
||||
private List<SessionRepositoryCustomizer<RedisIndexedSessionRepository>> sessionRepositoryCustomizers;
|
||||
private List<SessionRepositoryCustomizer<RedisSessionRepository>> sessionRepositoryCustomizers;
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
private StringValueResolver embeddedValueResolver;
|
||||
|
||||
@Bean
|
||||
public RedisIndexedSessionRepository sessionRepository() {
|
||||
RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
|
||||
RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate);
|
||||
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
if (this.indexResolver != null) {
|
||||
sessionRepository.setIndexResolver(this.indexResolver);
|
||||
}
|
||||
if (this.defaultRedisSerializer != null) {
|
||||
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
|
||||
}
|
||||
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||
public RedisSessionRepository sessionRepository() {
|
||||
RedisTemplate<String, Object> redisTemplate = createRedisTemplate();
|
||||
RedisSessionRepository sessionRepository = new RedisSessionRepository(redisTemplate);
|
||||
sessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(this.maxInactiveIntervalInSeconds));
|
||||
if (StringUtils.hasText(this.redisNamespace)) {
|
||||
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
|
||||
}
|
||||
sessionRepository.setFlushMode(this.flushMode);
|
||||
sessionRepository.setSaveMode(this.saveMode);
|
||||
int database = resolveDatabase();
|
||||
sessionRepository.setDatabase(database);
|
||||
this.sessionRepositoryCustomizers
|
||||
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisMessageListenerContainer springSessionRedisMessageListenerContainer(
|
||||
RedisIndexedSessionRepository sessionRepository) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(this.redisConnectionFactory);
|
||||
if (this.redisTaskExecutor != null) {
|
||||
container.setTaskExecutor(this.redisTaskExecutor);
|
||||
}
|
||||
if (this.redisSubscriptionExecutor != null) {
|
||||
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
|
||||
}
|
||||
container.addMessageListener(sessionRepository,
|
||||
Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()),
|
||||
new ChannelTopic(sessionRepository.getSessionExpiredChannel())));
|
||||
container.addMessageListener(sessionRepository,
|
||||
Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*")));
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public InitializingBean enableRedisKeyspaceNotificationsInitializer() {
|
||||
return new EnableRedisKeyspaceNotificationsInitializer(this.redisConnectionFactory, this.configureRedisAction);
|
||||
}
|
||||
|
||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
@@ -186,20 +119,6 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
public void setCleanupCron(String cleanupCron) {
|
||||
this.cleanupCron = cleanupCron;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to perform for configuring Redis.
|
||||
* @param configureRedisAction the configureRedis to set. The default is
|
||||
* {@link ConfigureNotifyKeyspaceEventsAction}.
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setConfigureRedisAction(ConfigureRedisAction configureRedisAction) {
|
||||
this.configureRedisAction = configureRedisAction;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setRedisConnectionFactory(
|
||||
@SpringSessionRedisConnectionFactory ObjectProvider<RedisConnectionFactory> springSessionRedisConnectionFactory,
|
||||
@@ -217,31 +136,9 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
this.defaultRedisSerializer = defaultRedisSerializer;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setIndexResolver(IndexResolver<Session> indexResolver) {
|
||||
this.indexResolver = indexResolver;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setSessionRepositoryCustomizer(
|
||||
ObjectProvider<SessionRepositoryCustomizer<RedisIndexedSessionRepository>> sessionRepositoryCustomizers) {
|
||||
ObjectProvider<SessionRepositoryCustomizer<RedisSessionRepository>> sessionRepositoryCustomizers) {
|
||||
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -273,14 +170,10 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
}
|
||||
this.flushMode = flushMode;
|
||||
this.saveMode = attributes.getEnum("saveMode");
|
||||
String cleanupCron = attributes.getString("cleanupCron");
|
||||
if (StringUtils.hasText(cleanupCron)) {
|
||||
this.cleanupCron = cleanupCron;
|
||||
}
|
||||
}
|
||||
|
||||
private RedisTemplate<Object, Object> createRedisTemplate() {
|
||||
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
||||
private RedisTemplate<String, Object> createRedisTemplate() {
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
if (this.defaultRedisSerializer != null) {
|
||||
@@ -292,77 +185,4 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
private int resolveDatabase() {
|
||||
if (ClassUtils.isPresent("io.lettuce.core.RedisClient", null)
|
||||
&& this.redisConnectionFactory instanceof LettuceConnectionFactory) {
|
||||
return ((LettuceConnectionFactory) this.redisConnectionFactory).getDatabase();
|
||||
}
|
||||
if (ClassUtils.isPresent("redis.clients.jedis.Jedis", null)
|
||||
&& this.redisConnectionFactory instanceof JedisConnectionFactory) {
|
||||
return ((JedisConnectionFactory) this.redisConnectionFactory).getDatabase();
|
||||
}
|
||||
return RedisIndexedSessionRepository.DEFAULT_DATABASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that Redis is configured to send keyspace notifications. This is important
|
||||
* to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents.
|
||||
* Without the SessionDestroyedEvent resources may not get cleaned up properly. For
|
||||
* example, the mapping of the Session to WebSocket connections may not get cleaned
|
||||
* up.
|
||||
*/
|
||||
static class EnableRedisKeyspaceNotificationsInitializer implements InitializingBean {
|
||||
|
||||
private final RedisConnectionFactory connectionFactory;
|
||||
|
||||
private ConfigureRedisAction configure;
|
||||
|
||||
EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory,
|
||||
ConfigureRedisAction configure) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.configure = configure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (this.configure == ConfigureRedisAction.NO_OP) {
|
||||
return;
|
||||
}
|
||||
RedisConnection connection = this.connectionFactory.getConnection();
|
||||
try {
|
||||
this.configure.configure(connection);
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
connection.close();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
LogFactory.getLog(getClass()).error("Error closing RedisConnection", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration of scheduled job for cleaning up expired sessions.
|
||||
*/
|
||||
@EnableScheduling
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class SessionCleanupConfiguration implements SchedulingConfigurer {
|
||||
|
||||
private final RedisIndexedSessionRepository sessionRepository;
|
||||
|
||||
SessionCleanupConfiguration(RedisIndexedSessionRepository sessionRepository) {
|
||||
this.sessionRepository = sessionRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.addCronTask(this.sessionRepository::cleanupExpiredSessions,
|
||||
RedisHttpSessionConfiguration.this.cleanupCron);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2014-2022 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
|
||||
*
|
||||
* https://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.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* Dynamically determines which session repository configuration to include using the
|
||||
* {@link EnableRedisHttpSession} annotation.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 3.0
|
||||
*/
|
||||
final class RedisHttpSessionConfigurationSelector implements ImportSelector {
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata importMetadata) {
|
||||
if (!importMetadata.hasAnnotation(EnableRedisHttpSession.class.getName())) {
|
||||
return new String[0];
|
||||
}
|
||||
EnableRedisHttpSession annotation = importMetadata.getAnnotations().get(EnableRedisHttpSession.class)
|
||||
.synthesize();
|
||||
if (annotation.enableIndexingAndEvents()) {
|
||||
return new String[] { RedisIndexedHttpSessionConfiguration.class.getName() };
|
||||
}
|
||||
else {
|
||||
return new String[] { RedisHttpSessionConfiguration.class.getName() };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Copyright 2014-2022 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
|
||||
*
|
||||
* https://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 java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.EmbeddedValueResolverAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.listener.PatternTopic;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
|
||||
import org.springframework.session.data.redis.config.ConfigureRedisAction;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.StringValueResolver;
|
||||
|
||||
/**
|
||||
* Exposes the {@link SessionRepositoryFilter} as a bean named
|
||||
* {@code springSessionRepositoryFilter}. In order to use this a single
|
||||
* {@link RedisConnectionFactory} must be exposed as a Bean.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 3.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class RedisIndexedHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
|
||||
|
||||
static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
|
||||
|
||||
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
|
||||
|
||||
private String redisNamespace = RedisIndexedSessionRepository.DEFAULT_NAMESPACE;
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private String cleanupCron = DEFAULT_CLEANUP_CRON;
|
||||
|
||||
private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();
|
||||
|
||||
private RedisConnectionFactory redisConnectionFactory;
|
||||
|
||||
private IndexResolver<Session> indexResolver;
|
||||
|
||||
private RedisSerializer<Object> defaultRedisSerializer;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private Executor redisTaskExecutor;
|
||||
|
||||
private Executor redisSubscriptionExecutor;
|
||||
|
||||
private List<SessionRepositoryCustomizer<RedisIndexedSessionRepository>> sessionRepositoryCustomizers;
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
private StringValueResolver embeddedValueResolver;
|
||||
|
||||
@Bean
|
||||
public RedisIndexedSessionRepository sessionRepository() {
|
||||
RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
|
||||
RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate);
|
||||
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
if (this.indexResolver != null) {
|
||||
sessionRepository.setIndexResolver(this.indexResolver);
|
||||
}
|
||||
if (this.defaultRedisSerializer != null) {
|
||||
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
|
||||
}
|
||||
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||
if (StringUtils.hasText(this.redisNamespace)) {
|
||||
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
|
||||
}
|
||||
sessionRepository.setFlushMode(this.flushMode);
|
||||
sessionRepository.setSaveMode(this.saveMode);
|
||||
int database = resolveDatabase();
|
||||
sessionRepository.setDatabase(database);
|
||||
this.sessionRepositoryCustomizers
|
||||
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisMessageListenerContainer springSessionRedisMessageListenerContainer(
|
||||
RedisIndexedSessionRepository sessionRepository) {
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(this.redisConnectionFactory);
|
||||
if (this.redisTaskExecutor != null) {
|
||||
container.setTaskExecutor(this.redisTaskExecutor);
|
||||
}
|
||||
if (this.redisSubscriptionExecutor != null) {
|
||||
container.setSubscriptionExecutor(this.redisSubscriptionExecutor);
|
||||
}
|
||||
container.addMessageListener(sessionRepository,
|
||||
Arrays.asList(new ChannelTopic(sessionRepository.getSessionDeletedChannel()),
|
||||
new ChannelTopic(sessionRepository.getSessionExpiredChannel())));
|
||||
container.addMessageListener(sessionRepository,
|
||||
Collections.singletonList(new PatternTopic(sessionRepository.getSessionCreatedChannelPrefix() + "*")));
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public InitializingBean enableRedisKeyspaceNotificationsInitializer() {
|
||||
return new EnableRedisKeyspaceNotificationsInitializer(this.redisConnectionFactory, this.configureRedisAction);
|
||||
}
|
||||
|
||||
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
|
||||
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
|
||||
}
|
||||
|
||||
public void setRedisNamespace(String namespace) {
|
||||
this.redisNamespace = namespace;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setRedisFlushMode(RedisFlushMode redisFlushMode) {
|
||||
Assert.notNull(redisFlushMode, "redisFlushMode cannot be null");
|
||||
setFlushMode(redisFlushMode.getFlushMode());
|
||||
}
|
||||
|
||||
public void setFlushMode(FlushMode flushMode) {
|
||||
Assert.notNull(flushMode, "flushMode cannot be null");
|
||||
this.flushMode = flushMode;
|
||||
}
|
||||
|
||||
public void setSaveMode(SaveMode saveMode) {
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
public void setCleanupCron(String cleanupCron) {
|
||||
this.cleanupCron = cleanupCron;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action to perform for configuring Redis.
|
||||
* @param configureRedisAction the configureRedis to set. The default is
|
||||
* {@link ConfigureNotifyKeyspaceEventsAction}.
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setConfigureRedisAction(ConfigureRedisAction configureRedisAction) {
|
||||
this.configureRedisAction = configureRedisAction;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setRedisConnectionFactory(
|
||||
@SpringSessionRedisConnectionFactory ObjectProvider<RedisConnectionFactory> springSessionRedisConnectionFactory,
|
||||
ObjectProvider<RedisConnectionFactory> redisConnectionFactory) {
|
||||
RedisConnectionFactory redisConnectionFactoryToUse = springSessionRedisConnectionFactory.getIfAvailable();
|
||||
if (redisConnectionFactoryToUse == null) {
|
||||
redisConnectionFactoryToUse = redisConnectionFactory.getObject();
|
||||
}
|
||||
this.redisConnectionFactory = redisConnectionFactoryToUse;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
@Qualifier("springSessionDefaultRedisSerializer")
|
||||
public void setDefaultRedisSerializer(RedisSerializer<Object> defaultRedisSerializer) {
|
||||
this.defaultRedisSerializer = defaultRedisSerializer;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setIndexResolver(IndexResolver<Session> indexResolver) {
|
||||
this.indexResolver = indexResolver;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setSessionRepositoryCustomizer(
|
||||
ObjectProvider<SessionRepositoryCustomizer<RedisIndexedSessionRepository>> sessionRepositoryCustomizers) {
|
||||
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmbeddedValueResolver(StringValueResolver resolver) {
|
||||
this.embeddedValueResolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
Map<String, Object> attributeMap = importMetadata
|
||||
.getAnnotationAttributes(EnableRedisHttpSession.class.getName());
|
||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap);
|
||||
this.maxInactiveIntervalInSeconds = attributes.getNumber("maxInactiveIntervalInSeconds");
|
||||
String redisNamespaceValue = attributes.getString("redisNamespace");
|
||||
if (StringUtils.hasText(redisNamespaceValue)) {
|
||||
this.redisNamespace = this.embeddedValueResolver.resolveStringValue(redisNamespaceValue);
|
||||
}
|
||||
FlushMode flushMode = attributes.getEnum("flushMode");
|
||||
RedisFlushMode redisFlushMode = attributes.getEnum("redisFlushMode");
|
||||
if (flushMode == FlushMode.ON_SAVE && redisFlushMode != RedisFlushMode.ON_SAVE) {
|
||||
flushMode = redisFlushMode.getFlushMode();
|
||||
}
|
||||
this.flushMode = flushMode;
|
||||
this.saveMode = attributes.getEnum("saveMode");
|
||||
}
|
||||
|
||||
private RedisTemplate<Object, Object> createRedisTemplate() {
|
||||
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
if (this.defaultRedisSerializer != null) {
|
||||
redisTemplate.setDefaultSerializer(this.defaultRedisSerializer);
|
||||
}
|
||||
redisTemplate.setConnectionFactory(this.redisConnectionFactory);
|
||||
redisTemplate.setBeanClassLoader(this.classLoader);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
private int resolveDatabase() {
|
||||
if (ClassUtils.isPresent("io.lettuce.core.RedisClient", null)
|
||||
&& this.redisConnectionFactory instanceof LettuceConnectionFactory) {
|
||||
return ((LettuceConnectionFactory) this.redisConnectionFactory).getDatabase();
|
||||
}
|
||||
if (ClassUtils.isPresent("redis.clients.jedis.Jedis", null)
|
||||
&& this.redisConnectionFactory instanceof JedisConnectionFactory) {
|
||||
return ((JedisConnectionFactory) this.redisConnectionFactory).getDatabase();
|
||||
}
|
||||
return RedisIndexedSessionRepository.DEFAULT_DATABASE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that Redis is configured to send keyspace notifications. This is important
|
||||
* to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents.
|
||||
* Without the SessionDestroyedEvent resources may not get cleaned up properly. For
|
||||
* example, the mapping of the Session to WebSocket connections may not get cleaned
|
||||
* up.
|
||||
*/
|
||||
static class EnableRedisKeyspaceNotificationsInitializer implements InitializingBean {
|
||||
|
||||
private final RedisConnectionFactory connectionFactory;
|
||||
|
||||
private ConfigureRedisAction configure;
|
||||
|
||||
EnableRedisKeyspaceNotificationsInitializer(RedisConnectionFactory connectionFactory,
|
||||
ConfigureRedisAction configure) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.configure = configure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (this.configure == ConfigureRedisAction.NO_OP) {
|
||||
return;
|
||||
}
|
||||
RedisConnection connection = this.connectionFactory.getConnection();
|
||||
try {
|
||||
this.configure.configure(connection);
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
connection.close();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
LogFactory.getLog(getClass()).error("Error closing RedisConnection", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration of scheduled job for cleaning up expired sessions.
|
||||
*/
|
||||
@EnableScheduling
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class SessionCleanupConfiguration implements SchedulingConfigurer {
|
||||
|
||||
private final RedisIndexedSessionRepository sessionRepository;
|
||||
|
||||
SessionCleanupConfiguration(RedisIndexedSessionRepository sessionRepository) {
|
||||
this.sessionRepository = sessionRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.addCronTask(this.sessionRepository::cleanupExpiredSessions,
|
||||
RedisIndexedHttpSessionConfiguration.this.cleanupCron);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -49,14 +49,14 @@ class EnableRedisKeyspaceNotificationsInitializerTests {
|
||||
@Captor
|
||||
ArgumentCaptor<String> options;
|
||||
|
||||
private RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer initializer;
|
||||
private RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer initializer;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
given(this.connectionFactory.getConnection()).willReturn(this.connection);
|
||||
|
||||
this.initializer = new RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer(
|
||||
this.initializer = new RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer(
|
||||
this.connectionFactory, new ConfigureNotifyKeyspaceEventsAction());
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.session.data.redis.config.annotation.web.http;
|
||||
|
||||
import java.util.Map;
|
||||
import java.time.Duration;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -34,15 +34,12 @@ import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.SubscriptionListener;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.RedisSessionRepository;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
@@ -62,9 +59,7 @@ import static org.mockito.Mockito.mock;
|
||||
*/
|
||||
class RedisHttpSessionConfigurationTests {
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
||||
|
||||
private static final String CLEANUP_CRON_EXPRESSION = "0 0 * * * *";
|
||||
private static final Duration MAX_INACTIVE_INTERVAL_DURATION = Duration.ofSeconds(600);
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@@ -100,7 +95,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
@Test
|
||||
void customFlushImmediately() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
@@ -108,7 +103,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
@Test
|
||||
void customFlushImmediatelyLegacy() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyLegacyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
@@ -116,7 +111,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
@Test
|
||||
void setCustomFlushImmediately() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
@@ -124,40 +119,22 @@ class RedisHttpSessionConfigurationTests {
|
||||
@Test
|
||||
void setCustomFlushImmediatelyLegacy() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetLegacyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customCleanupCronAnnotation() {
|
||||
registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionAnnotationConfiguration.class);
|
||||
|
||||
RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class);
|
||||
assertThat(configuration).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customCleanupCronSetter() {
|
||||
registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionSetterConfiguration.class);
|
||||
|
||||
RedisHttpSessionConfiguration configuration = this.context.getBean(RedisHttpSessionConfiguration.class);
|
||||
assertThat(configuration).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeAnnotation() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class);
|
||||
assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
assertThat(this.context.getBean(RedisSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeSetter() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class);
|
||||
assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
assertThat(this.context.getBean(RedisSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@@ -165,7 +142,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
void qualifiedConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
@@ -181,7 +158,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
void primaryConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, PrimaryConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("primaryRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
@@ -197,7 +174,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
void qualifiedAndPrimaryConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, QualifiedAndPrimaryConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
@@ -213,7 +190,7 @@ class RedisHttpSessionConfigurationTests {
|
||||
void namedConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, NamedConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository repository = this.context.getBean(RedisSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("redisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
@@ -232,32 +209,12 @@ class RedisHttpSessionConfigurationTests {
|
||||
.withMessageContaining("expected single matching bean but found 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customIndexResolverConfiguration() {
|
||||
registerAndRefresh(RedisConfig.class, CustomIndexResolverConfiguration.class);
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
IndexResolver<Session> indexResolver = this.context.getBean(IndexResolver.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(indexResolver).isNotNull();
|
||||
assertThat(repository).hasFieldOrPropertyWithValue("indexResolver", indexResolver);
|
||||
}
|
||||
|
||||
@Test // gh-1252
|
||||
void customRedisMessageListenerContainerConfig() {
|
||||
registerAndRefresh(RedisConfig.class, CustomRedisMessageListenerContainerConfig.class);
|
||||
Map<String, RedisMessageListenerContainer> beans = this.context
|
||||
.getBeansOfType(RedisMessageListenerContainer.class);
|
||||
assertThat(beans).hasSize(2);
|
||||
assertThat(beans).containsKeys("springSessionRedisMessageListenerContainer", "redisMessageListenerContainer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionRepositoryCustomizer() {
|
||||
registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
|
||||
assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
|
||||
MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
MAX_INACTIVE_INTERVAL_DURATION);
|
||||
}
|
||||
|
||||
private void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||
@@ -338,20 +295,6 @@ class RedisHttpSessionConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession(cleanupCron = CLEANUP_CRON_EXPRESSION)
|
||||
static class CustomCleanupCronExpressionAnnotationConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomCleanupCronExpressionSetterConfiguration extends RedisHttpSessionConfiguration {
|
||||
|
||||
CustomCleanupCronExpressionSetterConfiguration() {
|
||||
setCleanupCron(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession(saveMode = SaveMode.ALWAYS)
|
||||
static class CustomSaveModeExpressionAnnotationConfiguration {
|
||||
|
||||
@@ -442,43 +385,20 @@ class RedisHttpSessionConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession
|
||||
static class CustomIndexResolverConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
IndexResolver<Session> indexResolver() {
|
||||
return mock(IndexResolver.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession
|
||||
static class CustomRedisMessageListenerContainerConfig {
|
||||
|
||||
@Bean
|
||||
RedisMessageListenerContainer redisMessageListenerContainer() {
|
||||
return mock(RedisMessageListenerContainer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession
|
||||
static class SessionRepositoryCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
SessionRepositoryCustomizer<RedisIndexedSessionRepository> sessionRepositoryCustomizerOne() {
|
||||
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0);
|
||||
SessionRepositoryCustomizer<RedisSessionRepository> sessionRepositoryCustomizerOne() {
|
||||
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(Duration.ZERO);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
SessionRepositoryCustomizer<RedisIndexedSessionRepository> sessionRepositoryCustomizerTwo() {
|
||||
SessionRepositoryCustomizer<RedisSessionRepository> sessionRepositoryCustomizerTwo() {
|
||||
return (sessionRepository) -> sessionRepository
|
||||
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_DURATION);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -24,7 +24,7 @@ import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.session.data.redis.config.ConfigureRedisAction;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer;
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration.EnableRedisKeyspaceNotificationsInitializer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
class RedisHttpSessionConfigurationMockTests {
|
||||
class RedisIndexedHttpSessionConfigurationMockTests {
|
||||
|
||||
@Mock
|
||||
RedisConnectionFactory factory;
|
||||
@@ -48,7 +48,7 @@ import static org.mockito.Mockito.verify;
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class RedisHttpSessionConfigurationOverrideSessionTaskExecutor {
|
||||
class RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutor {
|
||||
|
||||
@Autowired
|
||||
RedisMessageListenerContainer redisMessageListenerContainer;
|
||||
@@ -61,7 +61,7 @@ class RedisHttpSessionConfigurationOverrideSessionTaskExecutor {
|
||||
verify(this.springSessionRedisTaskExecutor, times(1)).execute(any(Runnable.class));
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@@ -50,7 +50,7 @@ import static org.mockito.Mockito.verify;
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
class RedisHttpSessionConfigurationOverrideSessionTaskExecutors {
|
||||
class RedisIndexedHttpSessionConfigurationOverrideSessionTaskExecutors {
|
||||
|
||||
@Autowired
|
||||
RedisMessageListenerContainer redisMessageListenerContainer;
|
||||
@@ -67,7 +67,7 @@ class RedisHttpSessionConfigurationOverrideSessionTaskExecutors {
|
||||
verify(this.springSessionRedisTaskExecutor, never()).execute(any(Runnable.class));
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
* Copyright 2014-2022 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
|
||||
*
|
||||
* https://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 java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.SubscriptionListener;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.IndexResolver;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.config.SessionRepositoryCustomizer;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RedisIndexedHttpSessionConfiguration}.
|
||||
*/
|
||||
class RedisIndexedHttpSessionConfigurationTests {
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
||||
|
||||
private static final String CLEANUP_CRON_EXPRESSION = "0 0 * * * *";
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void after() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveValue() {
|
||||
registerAndRefresh(RedisConfig.class, CustomRedisHttpSessionConfiguration.class);
|
||||
RedisIndexedHttpSessionConfiguration configuration = this.context
|
||||
.getBean(RedisIndexedHttpSessionConfiguration.class);
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("myRedisNamespace");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveValueByPlaceholder() {
|
||||
this.context
|
||||
.setEnvironment(new MockEnvironment().withProperty("session.redis.namespace", "customRedisNamespace"));
|
||||
registerAndRefresh(RedisConfig.class, PropertySourceConfiguration.class,
|
||||
CustomRedisHttpSessionConfiguration2.class);
|
||||
RedisIndexedHttpSessionConfiguration configuration = this.context
|
||||
.getBean(RedisIndexedHttpSessionConfiguration.class);
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("customRedisNamespace");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customFlushImmediately() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customFlushImmediatelyLegacy() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyLegacyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setCustomFlushImmediately() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setCustomFlushImmediatelyLegacy() {
|
||||
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetLegacyConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
assertThat(sessionRepository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customCleanupCronSetter() {
|
||||
registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionSetterConfiguration.class);
|
||||
|
||||
RedisIndexedHttpSessionConfiguration configuration = this.context
|
||||
.getBean(RedisIndexedHttpSessionConfiguration.class);
|
||||
assertThat(configuration).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeAnnotation() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class);
|
||||
assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeSetter() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class);
|
||||
assertThat(this.context.getBean(RedisIndexedSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifiedConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(redisConnectionFactory).isNotNull();
|
||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
||||
"sessionRedisOperations");
|
||||
assertThat(redisOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||
.isEqualTo(redisConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primaryConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, PrimaryConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("primaryRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(redisConnectionFactory).isNotNull();
|
||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
||||
"sessionRedisOperations");
|
||||
assertThat(redisOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||
.isEqualTo(redisConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifiedAndPrimaryConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, QualifiedAndPrimaryConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("qualifiedRedisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(redisConnectionFactory).isNotNull();
|
||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
||||
"sessionRedisOperations");
|
||||
assertThat(redisOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||
.isEqualTo(redisConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, NamedConnectionFactoryRedisConfig.class);
|
||||
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
RedisConnectionFactory redisConnectionFactory = this.context.getBean("redisConnectionFactory",
|
||||
RedisConnectionFactory.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(redisConnectionFactory).isNotNull();
|
||||
RedisOperations redisOperations = (RedisOperations) ReflectionTestUtils.getField(repository,
|
||||
"sessionRedisOperations");
|
||||
assertThat(redisOperations).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(redisOperations, "connectionFactory"))
|
||||
.isEqualTo(redisConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleConnectionFactoryRedisConfig() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> registerAndRefresh(RedisConfig.class, MultipleConnectionFactoryRedisConfig.class))
|
||||
.withMessageContaining("expected single matching bean but found 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customIndexResolverConfiguration() {
|
||||
registerAndRefresh(RedisConfig.class, CustomIndexResolverConfiguration.class);
|
||||
RedisIndexedSessionRepository repository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
IndexResolver<Session> indexResolver = this.context.getBean(IndexResolver.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(indexResolver).isNotNull();
|
||||
assertThat(repository).hasFieldOrPropertyWithValue("indexResolver", indexResolver);
|
||||
}
|
||||
|
||||
@Test // gh-1252
|
||||
void customRedisMessageListenerContainerConfig() {
|
||||
registerAndRefresh(RedisConfig.class, CustomRedisMessageListenerContainerConfig.class);
|
||||
Map<String, RedisMessageListenerContainer> beans = this.context
|
||||
.getBeansOfType(RedisMessageListenerContainer.class);
|
||||
assertThat(beans).hasSize(2);
|
||||
assertThat(beans).containsKeys("springSessionRedisMessageListenerContainer", "redisMessageListenerContainer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionRepositoryCustomizer() {
|
||||
registerAndRefresh(RedisConfig.class, SessionRepositoryCustomizerConfiguration.class);
|
||||
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
|
||||
assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
|
||||
MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
private void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||
this.context.register(annotatedClasses);
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
private static RedisConnectionFactory mockRedisConnectionFactory() {
|
||||
RedisConnectionFactory connectionFactoryMock = mock(RedisConnectionFactory.class);
|
||||
RedisConnection connectionMock = mock(RedisConnection.class);
|
||||
given(connectionFactoryMock.getConnection()).willReturn(connectionMock);
|
||||
|
||||
Properties keyspaceEventsConfig = new Properties();
|
||||
keyspaceEventsConfig.put("notify-keyspace-events", "KEA");
|
||||
given(connectionMock.getConfig("notify-keyspace-events")).willReturn(keyspaceEventsConfig);
|
||||
|
||||
willAnswer((it) -> {
|
||||
SubscriptionListener listener = it.getArgument(0);
|
||||
listener.onPatternSubscribed(it.getArgument(1), 0);
|
||||
listener.onChannelSubscribed("__keyevent@0__:del".getBytes(), 0);
|
||||
listener.onChannelSubscribed("__keyevent@0__:expired".getBytes(), 0);
|
||||
|
||||
return null;
|
||||
}).given(connectionMock).pSubscribe(any(), any());
|
||||
|
||||
return connectionFactoryMock;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class PropertySourceConfiguration {
|
||||
|
||||
@Bean
|
||||
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class RedisConfig {
|
||||
|
||||
@Bean
|
||||
RedisConnectionFactory defaultRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomFlushImmediatelySetConfiguration extends RedisIndexedHttpSessionConfiguration {
|
||||
|
||||
CustomFlushImmediatelySetConfiguration() {
|
||||
setFlushMode(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@SuppressWarnings("deprecation")
|
||||
static class CustomFlushImmediatelySetLegacyConfiguration extends RedisIndexedHttpSessionConfiguration {
|
||||
|
||||
CustomFlushImmediatelySetLegacyConfiguration() {
|
||||
setRedisFlushMode(RedisFlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE, enableIndexingAndEvents = true)
|
||||
static class CustomFlushImmediatelyConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE, enableIndexingAndEvents = true)
|
||||
@SuppressWarnings("deprecation")
|
||||
static class CustomFlushImmediatelyLegacyConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomCleanupCronExpressionSetterConfiguration extends RedisIndexedHttpSessionConfiguration {
|
||||
|
||||
CustomCleanupCronExpressionSetterConfiguration() {
|
||||
setCleanupCron(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession(saveMode = SaveMode.ALWAYS, enableIndexingAndEvents = true)
|
||||
static class CustomSaveModeExpressionAnnotationConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomSaveModeExpressionSetterConfiguration extends RedisIndexedHttpSessionConfiguration {
|
||||
|
||||
CustomSaveModeExpressionSetterConfiguration() {
|
||||
setSaveMode(SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class QualifiedConnectionFactoryRedisConfig {
|
||||
|
||||
@Bean
|
||||
@SpringSessionRedisConnectionFactory
|
||||
RedisConnectionFactory qualifiedRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class PrimaryConnectionFactoryRedisConfig {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
RedisConnectionFactory primaryRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class QualifiedAndPrimaryConnectionFactoryRedisConfig {
|
||||
|
||||
@Bean
|
||||
@SpringSessionRedisConnectionFactory
|
||||
RedisConnectionFactory qualifiedRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
RedisConnectionFactory primaryRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class NamedConnectionFactoryRedisConfig {
|
||||
|
||||
@Bean
|
||||
RedisConnectionFactory redisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class MultipleConnectionFactoryRedisConfig {
|
||||
|
||||
@Bean
|
||||
RedisConnectionFactory secondaryRedisConnectionFactory() {
|
||||
return mockRedisConnectionFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "myRedisNamespace", enableIndexingAndEvents = true)
|
||||
static class CustomRedisHttpSessionConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(redisNamespace = "${session.redis.namespace}", enableIndexingAndEvents = true)
|
||||
static class CustomRedisHttpSessionConfiguration2 {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class CustomIndexResolverConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
IndexResolver<Session> indexResolver() {
|
||||
return mock(IndexResolver.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class CustomRedisMessageListenerContainerConfig {
|
||||
|
||||
@Bean
|
||||
RedisMessageListenerContainer redisMessageListenerContainer() {
|
||||
return mock(RedisMessageListenerContainer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession(enableIndexingAndEvents = true)
|
||||
static class SessionRepositoryCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
@Order(0)
|
||||
SessionRepositoryCustomizer<RedisIndexedSessionRepository> sessionRepositoryCustomizerOne() {
|
||||
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
SessionRepositoryCustomizer<RedisIndexedSessionRepository> sessionRepositoryCustomizerTwo() {
|
||||
return (sessionRepository) -> sessionRepository
|
||||
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user