Skip to content

Commit 9862924

Browse files
committed
Add support for configuring Spring Session Redis repository
With Spring Session moving to RedisSessionRepository as the preferred session repository, Spring Boot auto-configuration should make it possible to easily switch back to the previous default (RedisIndexedSessionRepository). This commit introduces spring.session.redis.repository configuration property that allow selecting the desired Redis-backed session repository implementation. Closes spring-projectsgh-30673
1 parent 19a7fee commit 9862924

File tree

4 files changed

+129
-46
lines changed

4 files changed

+129
-46
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java

+54-20
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2323
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2424
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2526
import org.springframework.boot.autoconfigure.web.ServerProperties;
2627
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2728
import org.springframework.context.annotation.Bean;
@@ -32,6 +33,7 @@
3233
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
3334
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
3435
import org.springframework.session.data.redis.config.ConfigureRedisAction;
36+
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration;
3537
import org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration;
3638

3739
/**
@@ -50,30 +52,62 @@
5052
@EnableConfigurationProperties(RedisSessionProperties.class)
5153
class RedisSessionConfiguration {
5254

53-
@Bean
54-
@ConditionalOnMissingBean
55-
ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) {
56-
return switch (redisSessionProperties.getConfigureAction()) {
57-
case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsAction();
58-
case NONE -> ConfigureRedisAction.NO_OP;
59-
};
55+
@Configuration(proxyBeanMethods = false)
56+
@ConditionalOnProperty(prefix = "spring.session.redis", name = "repository", havingValue = "default",
57+
matchIfMissing = true)
58+
static class DefaultRedisSessionConfiguration {
59+
60+
@Configuration(proxyBeanMethods = false)
61+
public static class SpringBootRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration {
62+
63+
@Autowired
64+
public void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
65+
ServerProperties serverProperties) {
66+
Duration timeout = sessionProperties
67+
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout());
68+
if (timeout != null) {
69+
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
70+
}
71+
setRedisNamespace(redisSessionProperties.getNamespace());
72+
setFlushMode(redisSessionProperties.getFlushMode());
73+
setSaveMode(redisSessionProperties.getSaveMode());
74+
}
75+
76+
}
77+
6078
}
6179

6280
@Configuration(proxyBeanMethods = false)
63-
public static class SpringBootRedisHttpSessionConfiguration extends RedisIndexedHttpSessionConfiguration {
64-
65-
@Autowired
66-
public void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
67-
ServerProperties serverProperties) {
68-
Duration timeout = sessionProperties
69-
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout());
70-
if (timeout != null) {
71-
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
81+
@ConditionalOnProperty(prefix = "spring.session.redis", name = "repository", havingValue = "indexed")
82+
static class IndexedRedisSessionConfiguration {
83+
84+
@Bean
85+
@ConditionalOnMissingBean
86+
ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) {
87+
return switch (redisSessionProperties.getConfigureAction()) {
88+
case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsAction();
89+
case NONE -> ConfigureRedisAction.NO_OP;
90+
};
91+
}
92+
93+
@Configuration(proxyBeanMethods = false)
94+
public static class SpringBootRedisIndexedHttpSessionConfiguration
95+
extends RedisIndexedHttpSessionConfiguration {
96+
97+
@Autowired
98+
public void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
99+
ServerProperties serverProperties) {
100+
Duration timeout = sessionProperties
101+
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout());
102+
if (timeout != null) {
103+
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
104+
}
105+
setRedisNamespace(redisSessionProperties.getNamespace());
106+
setFlushMode(redisSessionProperties.getFlushMode());
107+
setSaveMode(redisSessionProperties.getSaveMode());
108+
setCleanupCron(redisSessionProperties.getCleanupCron());
72109
}
73-
setRedisNamespace(redisSessionProperties.getNamespace());
74-
setFlushMode(redisSessionProperties.getFlushMode());
75-
setSaveMode(redisSessionProperties.getSaveMode());
76-
setCleanupCron(redisSessionProperties.getCleanupCron());
110+
77111
}
78112

79113
}

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

+15-1
Original file line numberDiff line numberDiff line change
@@ -2757,6 +2757,20 @@
27572757
"name": "spring.session.redis.flush-mode",
27582758
"defaultValue": "on-save"
27592759
},
2760+
{
2761+
"name": "spring.session.redis.repository",
2762+
"description": "Redis session repository implementation to use.",
2763+
"values": [
2764+
{
2765+
"value": "default",
2766+
"description": "Use default Redis session repository."
2767+
},
2768+
{
2769+
"value": "indexed",
2770+
"description": "Use indexed Redis session repository."
2771+
}
2772+
]
2773+
},
27602774
{
27612775
"name": "spring.session.redis.save-mode",
27622776
"defaultValue": "on-set-attribute"
@@ -3362,4 +3376,4 @@
33623376
]
33633377
}
33643378
]
3365-
}
3379+
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java

+59-25
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
import org.springframework.boot.autoconfigure.AutoConfigurations;
2727
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
28-
import org.springframework.boot.autoconfigure.session.RedisSessionConfiguration.SpringBootRedisHttpSessionConfiguration;
28+
import org.springframework.boot.autoconfigure.session.RedisSessionConfiguration.IndexedRedisSessionConfiguration.SpringBootRedisIndexedHttpSessionConfiguration;
2929
import org.springframework.boot.autoconfigure.web.ServerProperties;
3030
import org.springframework.boot.test.context.FilteredClassLoader;
3131
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
@@ -38,6 +38,7 @@
3838
import org.springframework.session.SaveMode;
3939
import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
4040
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
41+
import org.springframework.session.data.redis.RedisSessionRepository;
4142
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
4243
import org.springframework.session.data.redis.config.ConfigureRedisAction;
4344
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
@@ -70,17 +71,17 @@ void defaultConfig() {
7071
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
7172
"spring.data.redis.port=" + redis.getFirstMappedPort())
7273
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
73-
.run(validateSpringSessionUsesRedis("spring:session:event:0:created:", FlushMode.ON_SAVE,
74-
SaveMode.ON_SET_ATTRIBUTE, "0 * * * * *"));
74+
.run(validateSpringSessionUsesDefaultRedis("spring:session:", FlushMode.ON_SAVE,
75+
SaveMode.ON_SET_ATTRIBUTE));
7576
}
7677

7778
@Test
7879
void redisTakesPrecedenceMultipleImplementations() {
7980
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
8081
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
8182
"spring.data.redis.port=" + redis.getFirstMappedPort())
82-
.run(validateSpringSessionUsesRedis("spring:session:event:0:created:", FlushMode.ON_SAVE,
83-
SaveMode.ON_SET_ATTRIBUTE, "0 * * * * *"));
83+
.run(validateSpringSessionUsesDefaultRedis("spring:session:", FlushMode.ON_SAVE,
84+
SaveMode.ON_SET_ATTRIBUTE));
8485
}
8586

8687
@Test
@@ -89,64 +90,97 @@ void defaultConfigWithCustomTimeout() {
8990
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
9091
"spring.data.redis.port=" + redis.getFirstMappedPort(), "spring.session.timeout=1m")
9192
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)).run((context) -> {
92-
RedisIndexedSessionRepository repository = validateSessionRepository(context,
93-
RedisIndexedSessionRepository.class);
94-
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
93+
RedisSessionRepository repository = validateSessionRepository(context,
94+
RedisSessionRepository.class);
95+
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
96+
Duration.ofMinutes(1));
9597
});
9698
}
9799

98100
@Test
99-
void redisSessionStoreWithCustomizations() {
101+
void defaultRedisSessionStoreWithCustomizations() {
100102
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
101103
.withPropertyValues("spring.session.redis.namespace=foo", "spring.session.redis.flush-mode=immediate",
102-
"spring.session.redis.save-mode=on-get-attribute",
104+
"spring.session.redis.save-mode=on-get-attribute", "spring.data.redis.host=" + redis.getHost(),
105+
"spring.data.redis.port=" + redis.getFirstMappedPort())
106+
.run(validateSpringSessionUsesDefaultRedis("foo:", FlushMode.IMMEDIATE, SaveMode.ON_GET_ATTRIBUTE));
107+
}
108+
109+
@Test
110+
void indexedRedisSessionDefaultConfig() {
111+
this.contextRunner.withPropertyValues("spring.session.redis.repository=indexed",
112+
"spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort())
113+
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
114+
.run(validateSpringSessionUsesIndexedRedis("spring:session:", FlushMode.ON_SAVE,
115+
SaveMode.ON_SET_ATTRIBUTE, "0 * * * * *"));
116+
}
117+
118+
@Test
119+
void indexedRedisSessionStoreWithCustomizations() {
120+
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
121+
.withPropertyValues("spring.session.redis.repository=indexed", "spring.session.redis.namespace=foo",
122+
"spring.session.redis.flush-mode=immediate", "spring.session.redis.save-mode=on-get-attribute",
103123
"spring.session.redis.cleanup-cron=0 0 12 * * *", "spring.data.redis.host=" + redis.getHost(),
104124
"spring.data.redis.port=" + redis.getFirstMappedPort())
105-
.run(validateSpringSessionUsesRedis("foo:event:0:created:", FlushMode.IMMEDIATE,
106-
SaveMode.ON_GET_ATTRIBUTE, "0 0 12 * * *"));
125+
.run(validateSpringSessionUsesIndexedRedis("foo:", FlushMode.IMMEDIATE, SaveMode.ON_GET_ATTRIBUTE,
126+
"0 0 12 * * *"));
107127
}
108128

109129
@Test
110-
void redisSessionWithConfigureActionNone() {
130+
void indexedRedisSessionWithConfigureActionNone() {
111131
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
112-
.withPropertyValues("spring.session.redis.configure-action=none",
113-
"spring.data.redis.host=" + redis.getHost(),
132+
.withPropertyValues("spring.session.redis.repository=indexed",
133+
"spring.session.redis.configure-action=none", "spring.data.redis.host=" + redis.getHost(),
114134
"spring.data.redis.port=" + redis.getFirstMappedPort())
115135
.run(validateStrategy(ConfigureRedisAction.NO_OP.getClass()));
116136
}
117137

118138
@Test
119-
void redisSessionWithDefaultConfigureActionNone() {
139+
void indexedRedisSessionWithDefaultConfigureActionNone() {
120140
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
121-
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
141+
.withPropertyValues("spring.session.redis.repository=indexed",
142+
"spring.data.redis.host=" + redis.getHost(),
122143
"spring.data.redis.port=" + redis.getFirstMappedPort())
123144
.run(validateStrategy(ConfigureNotifyKeyspaceEventsAction.class,
124145
entry("notify-keyspace-events", "gxE")));
125146
}
126147

127148
@Test
128-
void redisSessionWithCustomConfigureRedisActionBean() {
149+
void indexedRedisSessionWithCustomConfigureRedisActionBean() {
129150
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
130151
.withUserConfiguration(MaxEntriesRedisAction.class)
131-
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
152+
.withPropertyValues("spring.session.redis.repository=indexed",
153+
"spring.data.redis.host=" + redis.getHost(),
132154
"spring.data.redis.port=" + redis.getFirstMappedPort())
133155
.run(validateStrategy(MaxEntriesRedisAction.class, entry("set-max-intset-entries", "1024")));
134156

135157
}
136158

137-
private ContextConsumer<AssertableWebApplicationContext> validateSpringSessionUsesRedis(
138-
String sessionCreatedChannelPrefix, FlushMode flushMode, SaveMode saveMode, String cleanupCron) {
159+
private ContextConsumer<AssertableWebApplicationContext> validateSpringSessionUsesDefaultRedis(String keyNamespace,
160+
FlushMode flushMode, SaveMode saveMode) {
161+
return (context) -> {
162+
RedisSessionRepository repository = validateSessionRepository(context, RedisSessionRepository.class);
163+
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
164+
new ServerProperties().getServlet().getSession().getTimeout());
165+
assertThat(repository).hasFieldOrPropertyWithValue("keyNamespace", keyNamespace);
166+
assertThat(repository).hasFieldOrPropertyWithValue("flushMode", flushMode);
167+
assertThat(repository).hasFieldOrPropertyWithValue("saveMode", saveMode);
168+
};
169+
}
170+
171+
private ContextConsumer<AssertableWebApplicationContext> validateSpringSessionUsesIndexedRedis(String keyNamespace,
172+
FlushMode flushMode, SaveMode saveMode, String cleanupCron) {
139173
return (context) -> {
140174
RedisIndexedSessionRepository repository = validateSessionRepository(context,
141175
RedisIndexedSessionRepository.class);
142176
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
143177
(int) new ServerProperties().getServlet().getSession().getTimeout().getSeconds());
144-
assertThat(repository.getSessionCreatedChannelPrefix()).isEqualTo(sessionCreatedChannelPrefix);
178+
assertThat(repository).hasFieldOrPropertyWithValue("namespace", keyNamespace);
145179
assertThat(repository).hasFieldOrPropertyWithValue("flushMode", flushMode);
146-
SpringBootRedisHttpSessionConfiguration configuration = context
147-
.getBean(SpringBootRedisHttpSessionConfiguration.class);
148-
assertThat(configuration).hasFieldOrPropertyWithValue("cleanupCron", cleanupCron);
149180
assertThat(repository).hasFieldOrPropertyWithValue("saveMode", saveMode);
181+
SpringBootRedisIndexedHttpSessionConfiguration configuration = context
182+
.getBean(SpringBootRedisIndexedHttpSessionConfiguration.class);
183+
assertThat(configuration).hasFieldOrPropertyWithValue("cleanupCron", cleanupCron);
150184
};
151185
}
152186

Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
management.endpoints.web.exposure.include=*
22
spring.security.user.name=user
33
spring.security.user.password=password
4+
spring.session.redis.repository=indexed

0 commit comments

Comments
 (0)