Skip to content

Commit 3093380

Browse files
vpavicwilkinsona
authored andcommitted
Add property to configure Spring Session Redis repository type
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 allows selecting the desired Redis-backed session repository implementation. See gh-32205
1 parent 6175c42 commit 3093380

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

Lines changed: 54 additions & 20 deletions
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

Lines changed: 15 additions & 1 deletion
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

Lines changed: 59 additions & 25 deletions
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)