diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java index 78282c76cd8..78e2d53cf2b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java @@ -22,13 +22,17 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.session.RedisSessionProperties.ConfigurationStrategy; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.session.SessionRepository; import org.springframework.session.data.redis.RedisOperationsSessionRepository; +import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction; +import org.springframework.session.data.redis.config.ConfigureRedisAction; import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration; /** @@ -62,6 +66,19 @@ class RedisSessionConfiguration { setCleanupCron(redisSessionProperties.getCleanupCron()); } + @Bean + @ConditionalOnMissingBean + public ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) { + ConfigurationStrategy strategy = redisSessionProperties.getConfigurationStrategy(); + if (strategy == ConfigurationStrategy.NOTIFY_KEYSPACE_EVENTS) { + return new ConfigureNotifyKeyspaceEventsAction(); + } + if (strategy == ConfigurationStrategy.NO_OP) { + return ConfigureRedisAction.NO_OP; + } + throw new IllegalStateException("Strategy '" + strategy + "' is not supported."); + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.java index a617480c8eb..aebe8b11842 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2019 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. @@ -40,6 +40,11 @@ public class RedisSessionProperties { */ private RedisFlushMode flushMode = RedisFlushMode.ON_SAVE; + /** + * Allows specifying a strategy for configuring and validating Redis. + */ + private ConfigurationStrategy configurationStrategy = ConfigurationStrategy.NOTIFY_KEYSPACE_EVENTS; + /** * Cron expression for expired session cleanup job. */ @@ -69,4 +74,29 @@ public class RedisSessionProperties { this.cleanupCron = cleanupCron; } + public ConfigurationStrategy getConfigurationStrategy() { + return this.configurationStrategy; + } + + public void setConfigurationStrategy(ConfigurationStrategy configurationStrategy) { + this.configurationStrategy = configurationStrategy; + } + + /** + * Allows specifying a strategy for configuring and validating Redis. + */ + public enum ConfigurationStrategy { + + /** + * Do nothing. + */ + NO_OP, + /** + * Ensures that Redis Keyspace events for Generic commands and Expired events are + * enabled. + */ + NOTIFY_KEYSPACE_EVENTS + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java index b96028db238..a9a6220198d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.session; +import java.util.Map; + import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; @@ -28,13 +30,18 @@ import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.testsupport.testcontainers.DisabledWithoutDockerTestcontainers; import org.springframework.boot.testsupport.testcontainers.RedisContainer; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.session.data.mongo.MongoOperationsSessionRepository; import org.springframework.session.data.redis.RedisFlushMode; import org.springframework.session.data.redis.RedisOperationsSessionRepository; +import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction; +import org.springframework.session.data.redis.config.ConfigureRedisAction; import org.springframework.session.hazelcast.HazelcastSessionRepository; import org.springframework.session.jdbc.JdbcOperationsSessionRepository; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; /** * Redis specific tests for {@link SessionAutoConfiguration}. @@ -81,6 +88,34 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio .run(validateSpringSessionUsesRedis("foo:event:0:created:", RedisFlushMode.IMMEDIATE, "0 0 12 * * *")); } + @Test + void redisSessionConfigureNoStrategy() { + this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withPropertyValues("spring.session.store-type=redis", + "spring.session.redis.configuration-strategy=no_op", + "spring.redis.port=" + redis.getFirstMappedPort()) + .run(validateStrategy(ConfigureRedisAction.NO_OP.getClass())); + } + + @Test + void redisSessionConfigureDefaultStrategy() { + this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withPropertyValues("spring.session.store-type=redis", + "spring.redis.port=" + redis.getFirstMappedPort()) + .run(validateStrategy(ConfigureNotifyKeyspaceEventsAction.class, + entry("notify-keyspace-events", "gxE"))); + } + + @Test + void redisSessionConfigureCustomStrategy() { + this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withUserConfiguration(MaxEntriesRedisAction.class) + .withPropertyValues("spring.session.store-type=redis", + "spring.redis.port=" + redis.getFirstMappedPort()) + .run(validateStrategy(MaxEntriesRedisAction.class, entry("set-max-intset-entries", "1024"))); + + } + private ContextConsumer validateSpringSessionUsesRedis( String sessionCreatedChannelPrefix, RedisFlushMode flushMode, String cleanupCron) { return (context) -> { @@ -94,4 +129,25 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio }; } + private ContextConsumer validateStrategy( + Class actionClass, Map.Entry... values) { + return (context) -> { + assertThat(context).hasSingleBean(ConfigureRedisAction.class).hasSingleBean(RedisConnectionFactory.class); + assertThat(context.getBean(ConfigureRedisAction.class)).isInstanceOf(actionClass); + RedisConnection connection = context.getBean(RedisConnectionFactory.class).getConnection(); + if (values.length > 0) { + assertThat(connection.getConfig("*")).contains(values); + } + }; + } + + static class MaxEntriesRedisAction implements ConfigureRedisAction { + + @Override + public void configure(RedisConnection connection) { + connection.setConfig("set-max-intset-entries", "1024"); + } + + } + }