Skip to content

Commit 4e8f035

Browse files
committed
Merge pull request #32205 from vpavic
* gh-32205: Polish "Add property to configure Spring Session Redis repository type" Add property to configure Spring Session Redis repository type Closes gh-32205
2 parents 6175c42 + 2977373 commit 4e8f035

File tree

5 files changed

+179
-50
lines changed

5 files changed

+179
-50
lines changed

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

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
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;
28+
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
2729
import org.springframework.context.annotation.Bean;
2830
import org.springframework.context.annotation.Configuration;
2931
import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -32,6 +34,7 @@
3234
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
3335
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
3436
import org.springframework.session.data.redis.config.ConfigureRedisAction;
37+
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration;
3538
import org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration;
3639

3740
/**
@@ -50,30 +53,71 @@
5053
@EnableConfigurationProperties(RedisSessionProperties.class)
5154
class RedisSessionConfiguration {
5255

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-
};
56+
@Configuration(proxyBeanMethods = false)
57+
@ConditionalOnProperty(prefix = "spring.session.redis", name = "repository-type", havingValue = "default",
58+
matchIfMissing = true)
59+
static class DefaultRedisSessionConfiguration {
60+
61+
@Configuration(proxyBeanMethods = false)
62+
static class SpringBootRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration {
63+
64+
@Autowired
65+
void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
66+
ServerProperties serverProperties) {
67+
String cleanupCron = redisSessionProperties.getCleanupCron();
68+
if (cleanupCron != null) {
69+
throw new InvalidConfigurationPropertyValueException("spring.session.redis.cleanup-cron",
70+
cleanupCron,
71+
"Cron-based cleanup is only supported when spring.session.redis.repository-type is set to "
72+
+ "indexed.");
73+
}
74+
Duration timeout = sessionProperties
75+
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout());
76+
if (timeout != null) {
77+
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
78+
}
79+
setRedisNamespace(redisSessionProperties.getNamespace());
80+
setFlushMode(redisSessionProperties.getFlushMode());
81+
setSaveMode(redisSessionProperties.getSaveMode());
82+
}
83+
84+
}
85+
6086
}
6187

6288
@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());
89+
@ConditionalOnProperty(prefix = "spring.session.redis", name = "repository-type", havingValue = "indexed")
90+
static class IndexedRedisSessionConfiguration {
91+
92+
@Bean
93+
@ConditionalOnMissingBean
94+
ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) {
95+
return switch (redisSessionProperties.getConfigureAction()) {
96+
case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsAction();
97+
case NONE -> ConfigureRedisAction.NO_OP;
98+
};
99+
}
100+
101+
@Configuration(proxyBeanMethods = false)
102+
static class SpringBootRedisIndexedHttpSessionConfiguration extends RedisIndexedHttpSessionConfiguration {
103+
104+
private static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
105+
106+
@Autowired
107+
void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
108+
ServerProperties serverProperties) {
109+
Duration timeout = sessionProperties
110+
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout());
111+
if (timeout != null) {
112+
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
113+
}
114+
setRedisNamespace(redisSessionProperties.getNamespace());
115+
setFlushMode(redisSessionProperties.getFlushMode());
116+
setSaveMode(redisSessionProperties.getSaveMode());
117+
String cleanupCron = redisSessionProperties.getCleanupCron();
118+
setCleanupCron((cleanupCron != null) ? cleanupCron : DEFAULT_CLEANUP_CRON);
72119
}
73-
setRedisNamespace(redisSessionProperties.getNamespace());
74-
setFlushMode(redisSessionProperties.getFlushMode());
75-
setSaveMode(redisSessionProperties.getSaveMode());
76-
setCleanupCron(redisSessionProperties.getCleanupCron());
120+
77121
}
78122

79123
}

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

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,8 +29,6 @@
2929
@ConfigurationProperties(prefix = "spring.session.redis")
3030
public class RedisSessionProperties {
3131

32-
private static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
33-
3432
/**
3533
* Namespace for keys used to store sessions.
3634
*/
@@ -55,9 +53,15 @@ public class RedisSessionProperties {
5553
private ConfigureAction configureAction = ConfigureAction.NOTIFY_KEYSPACE_EVENTS;
5654

5755
/**
58-
* Cron expression for expired session cleanup job.
56+
* Cron expression for expired session cleanup job. Only supported when
57+
* repository-type is set to indexed.
5958
*/
60-
private String cleanupCron = DEFAULT_CLEANUP_CRON;
59+
private String cleanupCron;
60+
61+
/**
62+
* Type of Redis session repository to configure.
63+
*/
64+
private RepositoryType repositoryType = RepositoryType.DEFAULT;
6165

6266
public String getNamespace() {
6367
return this.namespace;
@@ -99,6 +103,14 @@ public void setConfigureAction(ConfigureAction configureAction) {
99103
this.configureAction = configureAction;
100104
}
101105

106+
public RepositoryType getRepositoryType() {
107+
return this.repositoryType;
108+
}
109+
110+
public void setRepositoryType(RepositoryType repositoryType) {
111+
this.repositoryType = repositoryType;
112+
}
113+
102114
/**
103115
* Strategies for configuring and validating Redis.
104116
*/
@@ -117,4 +129,21 @@ public enum ConfigureAction {
117129

118130
}
119131

132+
/**
133+
* Type of Redis session repository to auto-configure.
134+
*/
135+
public enum RepositoryType {
136+
137+
/**
138+
* Auto-configure a RedisSessionRepository.
139+
*/
140+
DEFAULT,
141+
142+
/**
143+
* Auto-configure a RedisIndexedSessionRepository.
144+
*/
145+
INDEXED
146+
147+
}
148+
120149
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2749,6 +2749,10 @@
27492749
"name": "spring.session.jdbc.save-mode",
27502750
"defaultValue": "on-set-attribute"
27512751
},
2752+
{
2753+
"name": "spring.session.redis.cleanup-cron",
2754+
"defaultValue": "0 * * * * *"
2755+
},
27522756
{
27532757
"name": "spring.session.redis.configure-action",
27542758
"defaultValue": "notify-keyspace-events"
@@ -2757,6 +2761,10 @@
27572761
"name": "spring.session.redis.flush-mode",
27582762
"defaultValue": "on-save"
27592763
},
2764+
{
2765+
"name": "spring.session.redis.repository-type",
2766+
"defaultValue": "default"
2767+
},
27602768
{
27612769
"name": "spring.session.redis.save-mode",
27622770
"defaultValue": "on-set-attribute"
@@ -3362,4 +3370,4 @@
33623370
]
33633371
}
33643372
]
3365-
}
3373+
}

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

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
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;
30+
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
3031
import org.springframework.boot.test.context.FilteredClassLoader;
3132
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
3233
import org.springframework.boot.test.context.runner.ContextConsumer;
@@ -38,6 +39,7 @@
3839
import org.springframework.session.SaveMode;
3940
import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
4041
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
42+
import org.springframework.session.data.redis.RedisSessionRepository;
4143
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
4244
import org.springframework.session.data.redis.config.ConfigureRedisAction;
4345
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
@@ -70,17 +72,28 @@ void defaultConfig() {
7072
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
7173
"spring.data.redis.port=" + redis.getFirstMappedPort())
7274
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
73-
.run(validateSpringSessionUsesRedis("spring:session:event:0:created:", FlushMode.ON_SAVE,
74-
SaveMode.ON_SET_ATTRIBUTE, "0 * * * * *"));
75+
.run(validateSpringSessionUsesDefaultRedis("spring:session:", FlushMode.ON_SAVE,
76+
SaveMode.ON_SET_ATTRIBUTE));
77+
}
78+
79+
@Test
80+
void invalidConfigurationPropertyValueWhenDefaultConfigIsUsedWithCustomCronCleanup() {
81+
this.contextRunner.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
82+
"spring.data.redis.port=" + redis.getFirstMappedPort(), "spring.session.redis.cleanup-cron=0 0 * * * *")
83+
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)).run((context) -> {
84+
assertThat(context).hasFailed();
85+
assertThat(context.getStartupFailure())
86+
.hasRootCauseExactlyInstanceOf(InvalidConfigurationPropertyValueException.class);
87+
});
7588
}
7689

7790
@Test
7891
void redisTakesPrecedenceMultipleImplementations() {
7992
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
8093
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
8194
"spring.data.redis.port=" + redis.getFirstMappedPort())
82-
.run(validateSpringSessionUsesRedis("spring:session:event:0:created:", FlushMode.ON_SAVE,
83-
SaveMode.ON_SET_ATTRIBUTE, "0 * * * * *"));
95+
.run(validateSpringSessionUsesDefaultRedis("spring:session:", FlushMode.ON_SAVE,
96+
SaveMode.ON_SET_ATTRIBUTE));
8497
}
8598

8699
@Test
@@ -89,64 +102,98 @@ void defaultConfigWithCustomTimeout() {
89102
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
90103
"spring.data.redis.port=" + redis.getFirstMappedPort(), "spring.session.timeout=1m")
91104
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)).run((context) -> {
92-
RedisIndexedSessionRepository repository = validateSessionRepository(context,
93-
RedisIndexedSessionRepository.class);
94-
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
105+
RedisSessionRepository repository = validateSessionRepository(context,
106+
RedisSessionRepository.class);
107+
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
108+
Duration.ofMinutes(1));
95109
});
96110
}
97111

98112
@Test
99-
void redisSessionStoreWithCustomizations() {
113+
void defaultRedisSessionStoreWithCustomizations() {
100114
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
101115
.withPropertyValues("spring.session.redis.namespace=foo", "spring.session.redis.flush-mode=immediate",
116+
"spring.session.redis.save-mode=on-get-attribute", "spring.data.redis.host=" + redis.getHost(),
117+
"spring.data.redis.port=" + redis.getFirstMappedPort())
118+
.run(validateSpringSessionUsesDefaultRedis("foo:", FlushMode.IMMEDIATE, SaveMode.ON_GET_ATTRIBUTE));
119+
}
120+
121+
@Test
122+
void indexedRedisSessionDefaultConfig() {
123+
this.contextRunner.withPropertyValues("spring.session.redis.repository-type=indexed",
124+
"spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort())
125+
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
126+
.run(validateSpringSessionUsesIndexedRedis("spring:session:", FlushMode.ON_SAVE,
127+
SaveMode.ON_SET_ATTRIBUTE, "0 * * * * *"));
128+
}
129+
130+
@Test
131+
void indexedRedisSessionStoreWithCustomizations() {
132+
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
133+
.withPropertyValues("spring.session.redis.repository-type=indexed",
134+
"spring.session.redis.namespace=foo", "spring.session.redis.flush-mode=immediate",
102135
"spring.session.redis.save-mode=on-get-attribute",
103136
"spring.session.redis.cleanup-cron=0 0 12 * * *", "spring.data.redis.host=" + redis.getHost(),
104137
"spring.data.redis.port=" + redis.getFirstMappedPort())
105-
.run(validateSpringSessionUsesRedis("foo:event:0:created:", FlushMode.IMMEDIATE,
106-
SaveMode.ON_GET_ATTRIBUTE, "0 0 12 * * *"));
138+
.run(validateSpringSessionUsesIndexedRedis("foo:", FlushMode.IMMEDIATE, SaveMode.ON_GET_ATTRIBUTE,
139+
"0 0 12 * * *"));
107140
}
108141

109142
@Test
110-
void redisSessionWithConfigureActionNone() {
143+
void indexedRedisSessionWithConfigureActionNone() {
111144
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
112-
.withPropertyValues("spring.session.redis.configure-action=none",
113-
"spring.data.redis.host=" + redis.getHost(),
145+
.withPropertyValues("spring.session.redis.repository-type=indexed",
146+
"spring.session.redis.configure-action=none", "spring.data.redis.host=" + redis.getHost(),
114147
"spring.data.redis.port=" + redis.getFirstMappedPort())
115148
.run(validateStrategy(ConfigureRedisAction.NO_OP.getClass()));
116149
}
117150

118151
@Test
119-
void redisSessionWithDefaultConfigureActionNone() {
152+
void indexedRedisSessionWithDefaultConfigureActionNone() {
120153
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
121-
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
154+
.withPropertyValues("spring.session.redis.repository-type=indexed",
155+
"spring.data.redis.host=" + redis.getHost(),
122156
"spring.data.redis.port=" + redis.getFirstMappedPort())
123157
.run(validateStrategy(ConfigureNotifyKeyspaceEventsAction.class,
124158
entry("notify-keyspace-events", "gxE")));
125159
}
126160

127161
@Test
128-
void redisSessionWithCustomConfigureRedisActionBean() {
162+
void indexedRedisSessionWithCustomConfigureRedisActionBean() {
129163
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
130164
.withUserConfiguration(MaxEntriesRedisAction.class)
131-
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
165+
.withPropertyValues("spring.session.redis.repository-type=indexed",
166+
"spring.data.redis.host=" + redis.getHost(),
132167
"spring.data.redis.port=" + redis.getFirstMappedPort())
133168
.run(validateStrategy(MaxEntriesRedisAction.class, entry("set-max-intset-entries", "1024")));
134169

135170
}
136171

137-
private ContextConsumer<AssertableWebApplicationContext> validateSpringSessionUsesRedis(
138-
String sessionCreatedChannelPrefix, FlushMode flushMode, SaveMode saveMode, String cleanupCron) {
172+
private ContextConsumer<AssertableWebApplicationContext> validateSpringSessionUsesDefaultRedis(String keyNamespace,
173+
FlushMode flushMode, SaveMode saveMode) {
174+
return (context) -> {
175+
RedisSessionRepository repository = validateSessionRepository(context, RedisSessionRepository.class);
176+
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
177+
new ServerProperties().getServlet().getSession().getTimeout());
178+
assertThat(repository).hasFieldOrPropertyWithValue("keyNamespace", keyNamespace);
179+
assertThat(repository).hasFieldOrPropertyWithValue("flushMode", flushMode);
180+
assertThat(repository).hasFieldOrPropertyWithValue("saveMode", saveMode);
181+
};
182+
}
183+
184+
private ContextConsumer<AssertableWebApplicationContext> validateSpringSessionUsesIndexedRedis(String keyNamespace,
185+
FlushMode flushMode, SaveMode saveMode, String cleanupCron) {
139186
return (context) -> {
140187
RedisIndexedSessionRepository repository = validateSessionRepository(context,
141188
RedisIndexedSessionRepository.class);
142189
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
143190
(int) new ServerProperties().getServlet().getSession().getTimeout().getSeconds());
144-
assertThat(repository.getSessionCreatedChannelPrefix()).isEqualTo(sessionCreatedChannelPrefix);
191+
assertThat(repository).hasFieldOrPropertyWithValue("namespace", keyNamespace);
145192
assertThat(repository).hasFieldOrPropertyWithValue("flushMode", flushMode);
146-
SpringBootRedisHttpSessionConfiguration configuration = context
147-
.getBean(SpringBootRedisHttpSessionConfiguration.class);
148-
assertThat(configuration).hasFieldOrPropertyWithValue("cleanupCron", cleanupCron);
149193
assertThat(repository).hasFieldOrPropertyWithValue("saveMode", saveMode);
194+
SpringBootRedisIndexedHttpSessionConfiguration configuration = context
195+
.getBean(SpringBootRedisIndexedHttpSessionConfiguration.class);
196+
assertThat(configuration).hasFieldOrPropertyWithValue("cleanupCron", cleanupCron);
150197
};
151198
}
152199

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-type=indexed

0 commit comments

Comments
 (0)