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 78282c76cd..810916f627 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 @@ -23,12 +23,15 @@ 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.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; /** @@ -48,6 +51,19 @@ import org.springframework.session.data.redis.config.annotation.web.http.RedisHt @EnableConfigurationProperties(RedisSessionProperties.class) class RedisSessionConfiguration { + @Bean + @ConditionalOnMissingBean + public ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) { + switch (redisSessionProperties.getConfigurationStrategy()) { + case NOTIFY_KEYSPACE_EVENTS: + return new ConfigureNotifyKeyspaceEventsAction(); + case NONE: + return ConfigureRedisAction.NO_OP; + } + throw new IllegalStateException("Unsupported redis configuration strategy '" + + redisSessionProperties.getConfigurationStrategy() + "'."); + } + @Configuration public static class SpringBootRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration { 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 a617480c8e..ed04db251a 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,12 @@ public class RedisSessionProperties { */ private RedisFlushMode flushMode = RedisFlushMode.ON_SAVE; + /** + * The configure action to apply when no user defined ConfigureRedisAction bean is + * present. + */ + private ConfigureAction configureAction = ConfigureAction.NOTIFY_KEYSPACE_EVENTS; + /** * Cron expression for expired session cleanup job. */ @@ -69,4 +75,30 @@ public class RedisSessionProperties { this.cleanupCron = cleanupCron; } + public ConfigureAction getConfigurationStrategy() { + return this.configureAction; + } + + public void setConfigurationStrategy(ConfigureAction configurationStrategy) { + this.configureAction = configurationStrategy; + } + + /** + * Strategies for configuring and validating Redis. + */ + public enum ConfigureAction { + + /** + * Ensure that Redis Keyspace events for Generic commands and Expired events are + * enabled. + */ + NOTIFY_KEYSPACE_EVENTS, + + /** + * No not attempt to apply any custom Redis configuration. + */ + NONE + + } + } 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 b96028db23..8bfbcc9167 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,33 @@ 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.configure-action=none", + "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 +128,26 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio }; } + private ContextConsumer validateStrategy( + Class expectedConfigureRedisActionType, Map.Entry... expectedConfig) { + return (context) -> { + assertThat(context).hasSingleBean(ConfigureRedisAction.class); + assertThat(context).hasSingleBean(RedisConnectionFactory.class); + assertThat(context.getBean(ConfigureRedisAction.class)).isInstanceOf(expectedConfigureRedisActionType); + RedisConnection connection = context.getBean(RedisConnectionFactory.class).getConnection(); + if (expectedConfig.length > 0) { + assertThat(connection.getConfig("*")).contains(expectedConfig); + } + }; + } + + static class MaxEntriesRedisAction implements ConfigureRedisAction { + + @Override + public void configure(RedisConnection connection) { + connection.setConfig("set-max-intset-entries", "1024"); + } + + } + }