Skip to content

Commit e050a92

Browse files
vpavicrwinch
authored andcommitted
Restructure Redis HttpSession configuration support
This commit restructures configuration support for Redis-backed HttpSession with aim to enable users to easily select the SessionRepository implementation they prefer to use. This is achieved by introducing [at]EnableRedisIndexedHttpSession annotation that can be used to configure RedisIndexedSessionRepository, while the existing [at]EnableRedisHttpSession will going forward configure RedisSessionRepository as the SessionRepository implementation used by Spring Session. Additionally, this also introduces AbstractRedisHttpSessionConfiguration as the base configuration class that manages common aspects of Redis-backed HttpSession support, which is then extended by more specific configuration classes that provide specific SessionRepository implementation. Closes gh-2122
1 parent 0c1dbc7 commit e050a92

File tree

45 files changed

+1055
-1105
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1055
-1105
lines changed

spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisIndexedSessionRepositoryITests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
import org.springframework.session.data.SessionEventRegistry;
4040
import org.springframework.session.data.redis.RedisIndexedSessionRepository.RedisSession;
4141
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
42-
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
42+
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession;
4343
import org.springframework.session.events.SessionCreatedEvent;
4444
import org.springframework.session.events.SessionDestroyedEvent;
4545
import org.springframework.test.context.ContextConfiguration;
@@ -691,7 +691,7 @@ private String getChangedSecurityName() {
691691
}
692692

693693
@Configuration
694-
@EnableRedisHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests")
694+
@EnableRedisIndexedHttpSession(redisNamespace = "RedisIndexedSessionRepositoryITests")
695695
static class Config extends BaseConfig {
696696

697697
@Bean

spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/RedisSessionRepositoryITests.java

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,10 @@
2626
import org.junit.jupiter.api.extension.ExtendWith;
2727

2828
import org.springframework.beans.factory.annotation.Autowired;
29-
import org.springframework.context.annotation.Bean;
3029
import org.springframework.context.annotation.Configuration;
31-
import org.springframework.data.redis.connection.RedisConnectionFactory;
32-
import org.springframework.data.redis.core.RedisTemplate;
3330
import org.springframework.session.MapSession;
34-
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
3531
import org.springframework.session.data.redis.RedisSessionRepository.RedisSession;
32+
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
3633
import org.springframework.test.context.ContextConfiguration;
3734
import org.springframework.test.context.junit.jupiter.SpringExtension;
3835
import org.springframework.test.context.web.WebAppConfiguration;
@@ -223,17 +220,9 @@ private static void updateSession(RedisSession session, Instant lastAccessedTime
223220
}
224221

225222
@Configuration
226-
@EnableSpringHttpSession
223+
@EnableRedisHttpSession
227224
static class Config extends BaseConfig {
228225

229-
@Bean
230-
RedisSessionRepository sessionRepository(RedisConnectionFactory redisConnectionFactory) {
231-
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
232-
redisTemplate.setConnectionFactory(redisConnectionFactory);
233-
redisTemplate.afterPropertiesSet();
234-
return new RedisSessionRepository(redisTemplate);
235-
}
236-
237226
}
238227

239228
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
@ExtendWith(SpringExtension.class)
4545
@ContextConfiguration
4646
@WebAppConfiguration
47-
class EnableRedisHttpSessionExpireSessionDestroyedTests<S extends Session> extends AbstractRedisITests {
47+
class EnableRedisIndexedHttpSessionExpireSessionDestroyedTests<S extends Session> extends AbstractRedisITests {
4848

4949
@Autowired
5050
private SessionRepository<S> repository;
@@ -113,7 +113,7 @@ void setLock(Object lock) {
113113
}
114114

115115
@Configuration
116-
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1)
116+
@EnableRedisIndexedHttpSession(maxInactiveIntervalInSeconds = 1)
117117
static class Config extends BaseConfig {
118118

119119
@Bean

spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/taskexecutor/RedisListenerContainerTaskExecutorITests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import org.springframework.data.redis.core.RedisOperations;
3333
import org.springframework.session.data.redis.AbstractRedisITests;
3434
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
35-
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
35+
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession;
3636
import org.springframework.test.context.ContextConfiguration;
3737
import org.springframework.test.context.junit.jupiter.SpringExtension;
3838
import org.springframework.test.context.web.WebAppConfiguration;
@@ -101,7 +101,7 @@ boolean taskDispatched() throws InterruptedException {
101101
}
102102

103103
@Configuration
104-
@EnableRedisHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests")
104+
@EnableRedisIndexedHttpSession(redisNamespace = "RedisListenerContainerTaskExecutorITests")
105105
static class Config extends BaseConfig {
106106

107107
@Bean

spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisIndexedSessionRepository.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2021 the original author or authors.
2+
* Copyright 2014-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.
@@ -288,7 +288,7 @@ public class RedisIndexedSessionRepository
288288

289289
private byte[] expiredKeyPrefixBytes;
290290

291-
private final RedisOperations<Object, Object> sessionRedisOperations;
291+
private final RedisOperations<String, Object> sessionRedisOperations;
292292

293293
private final RedisSessionExpirationPolicy expirationPolicy;
294294

@@ -314,7 +314,7 @@ public class RedisIndexedSessionRepository
314314
* @param sessionRedisOperations the {@link RedisOperations} to use for managing the
315315
* sessions. Cannot be null.
316316
*/
317-
public RedisIndexedSessionRepository(RedisOperations<Object, Object> sessionRedisOperations) {
317+
public RedisIndexedSessionRepository(RedisOperations<String, Object> sessionRedisOperations) {
318318
Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null");
319319
this.sessionRedisOperations = sessionRedisOperations;
320320
this.expirationPolicy = new RedisSessionExpirationPolicy(sessionRedisOperations, this::getExpirationsKey,
@@ -406,7 +406,7 @@ private void configureSessionChannels() {
406406
* Returns the {@link RedisOperations} used for sessions.
407407
* @return the {@link RedisOperations} used for sessions
408408
*/
409-
public RedisOperations<Object, Object> getSessionRedisOperations() {
409+
public RedisOperations<String, Object> getSessionRedisOperations() {
410410
return this.sessionRedisOperations;
411411
}
412412

@@ -454,7 +454,7 @@ public Map<String, RedisSession> findByIndexNameAndIndexValue(String indexName,
454454
* @return the Redis session
455455
*/
456456
private RedisSession getSession(String id, boolean allowExpired) {
457-
Map<Object, Object> entries = getSessionBoundHashOperations(id).entries();
457+
Map<String, Object> entries = getSessionBoundHashOperations(id).entries();
458458
if (entries.isEmpty()) {
459459
return null;
460460
}
@@ -467,10 +467,10 @@ private RedisSession getSession(String id, boolean allowExpired) {
467467
return result;
468468
}
469469

470-
private MapSession loadSession(String id, Map<Object, Object> entries) {
470+
private MapSession loadSession(String id, Map<String, Object> entries) {
471471
MapSession loaded = new MapSession(id);
472-
for (Map.Entry<Object, Object> entry : entries.entrySet()) {
473-
String key = (String) entry.getKey();
472+
for (Map.Entry<String, Object> entry : entries.entrySet()) {
473+
String key = entry.getKey();
474474
if (RedisSessionMapper.CREATION_TIME_KEY.equals(key)) {
475475
loaded.setCreationTime(Instant.ofEpochMilli((long) entry.getValue()));
476476
}
@@ -522,7 +522,7 @@ public void onMessage(Message message, byte[] pattern) {
522522
if (ByteUtils.startsWith(messageChannel, this.sessionCreatedChannelPrefixBytes)) {
523523
// TODO: is this thread safe?
524524
@SuppressWarnings("unchecked")
525-
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer.deserialize(message.getBody());
525+
Map<String, Object> loaded = (Map<String, Object>) this.defaultSerializer.deserialize(message.getBody());
526526
handleCreated(loaded, new String(messageChannel));
527527
return;
528528
}
@@ -571,7 +571,7 @@ private void cleanupPrincipalIndex(RedisSession session) {
571571
}
572572
}
573573

574-
private void handleCreated(Map<Object, Object> loaded, String channel) {
574+
private void handleCreated(Map<String, Object> loaded, String channel) {
575575
String id = channel.substring(channel.lastIndexOf(":") + 1);
576576
Session session = loadSession(id, loaded);
577577
publishEvent(new SessionCreatedEvent(this, session));
@@ -661,7 +661,7 @@ public String getSessionExpiredChannel() {
661661
* @param sessionId the id of the {@link Session} to work with
662662
* @return the {@link BoundHashOperations} to operate on a {@link Session}
663663
*/
664-
private BoundHashOperations<Object, Object, Object> getSessionBoundHashOperations(String sessionId) {
664+
private BoundHashOperations<String, String, Object> getSessionBoundHashOperations(String sessionId) {
665665
String key = getSessionKey(sessionId);
666666
return this.sessionRedisOperations.boundHashOps(key);
667667
}

spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisSessionExpirationPolicy.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2021 the original author or authors.
2+
* Copyright 2014-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.
@@ -52,13 +52,13 @@ final class RedisSessionExpirationPolicy {
5252

5353
private static final String SESSION_EXPIRES_PREFIX = "expires:";
5454

55-
private final RedisOperations<Object, Object> redis;
55+
private final RedisOperations<String, Object> redis;
5656

5757
private final Function<Long, String> lookupExpirationKey;
5858

5959
private final Function<String, String> lookupSessionKey;
6060

61-
RedisSessionExpirationPolicy(RedisOperations<Object, Object> sessionRedisOperations,
61+
RedisSessionExpirationPolicy(RedisOperations<String, Object> sessionRedisOperations,
6262
Function<Long, String> lookupExpirationKey, Function<String, String> lookupSessionKey) {
6363
super();
6464
this.redis = sessionRedisOperations;
@@ -96,7 +96,7 @@ void onExpirationUpdated(Long originalExpirationTimeInMilli, Session session) {
9696
}
9797

9898
String expireKey = getExpirationKey(toExpire);
99-
BoundSetOperations<Object, Object> expireOperations = this.redis.boundSetOps(expireKey);
99+
BoundSetOperations<String, Object> expireOperations = this.redis.boundSetOps(expireKey);
100100
expireOperations.add(keyToExpire);
101101

102102
long fiveMinutesAfterExpires = sessionExpireInSeconds + TimeUnit.MINUTES.toSeconds(5);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright 2014-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session.data.redis.config.annotation.web.http;
18+
19+
import java.util.List;
20+
import java.util.stream.Collectors;
21+
22+
import org.springframework.beans.factory.BeanClassLoaderAware;
23+
import org.springframework.beans.factory.ObjectProvider;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.beans.factory.annotation.Qualifier;
26+
import org.springframework.context.annotation.Configuration;
27+
import org.springframework.data.redis.connection.RedisConnectionFactory;
28+
import org.springframework.data.redis.core.RedisTemplate;
29+
import org.springframework.data.redis.serializer.RedisSerializer;
30+
import org.springframework.data.redis.serializer.StringRedisSerializer;
31+
import org.springframework.session.FlushMode;
32+
import org.springframework.session.MapSession;
33+
import org.springframework.session.SaveMode;
34+
import org.springframework.session.Session;
35+
import org.springframework.session.SessionRepository;
36+
import org.springframework.session.config.SessionRepositoryCustomizer;
37+
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
38+
import org.springframework.session.data.redis.RedisSessionRepository;
39+
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
40+
import org.springframework.util.Assert;
41+
42+
/**
43+
* Base configuration class for Redis based {@link SessionRepository} implementations.
44+
*
45+
* @param <T> the {@link SessionRepository} type
46+
* @author Vedran Pavic
47+
* @since 3.0.0
48+
* @see RedisHttpSessionConfiguration
49+
* @see RedisIndexedHttpSessionConfiguration
50+
* @see SpringSessionRedisConnectionFactory
51+
*/
52+
@Configuration(proxyBeanMethods = false)
53+
public abstract class AbstractRedisHttpSessionConfiguration<T extends SessionRepository<? extends Session>>
54+
extends SpringHttpSessionConfiguration implements BeanClassLoaderAware {
55+
56+
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
57+
58+
private String redisNamespace = RedisSessionRepository.DEFAULT_KEY_NAMESPACE;
59+
60+
private FlushMode flushMode = FlushMode.ON_SAVE;
61+
62+
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
63+
64+
private RedisConnectionFactory redisConnectionFactory;
65+
66+
private RedisSerializer<Object> defaultRedisSerializer;
67+
68+
private List<SessionRepositoryCustomizer<T>> sessionRepositoryCustomizers;
69+
70+
private ClassLoader classLoader;
71+
72+
public abstract T sessionRepository();
73+
74+
public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) {
75+
this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds;
76+
}
77+
78+
protected Integer getMaxInactiveIntervalInSeconds() {
79+
return this.maxInactiveIntervalInSeconds;
80+
}
81+
82+
public void setRedisNamespace(String namespace) {
83+
Assert.hasText(namespace, "namespace must not be empty");
84+
this.redisNamespace = namespace;
85+
}
86+
87+
protected String getRedisNamespace() {
88+
return this.redisNamespace;
89+
}
90+
91+
public void setFlushMode(FlushMode flushMode) {
92+
Assert.notNull(flushMode, "flushMode must not be null");
93+
this.flushMode = flushMode;
94+
}
95+
96+
protected FlushMode getFlushMode() {
97+
return this.flushMode;
98+
}
99+
100+
public void setSaveMode(SaveMode saveMode) {
101+
Assert.notNull(saveMode, "saveMode must not be null");
102+
this.saveMode = saveMode;
103+
}
104+
105+
protected SaveMode getSaveMode() {
106+
return this.saveMode;
107+
}
108+
109+
@Autowired
110+
public void setRedisConnectionFactory(
111+
@SpringSessionRedisConnectionFactory ObjectProvider<RedisConnectionFactory> springSessionRedisConnectionFactory,
112+
ObjectProvider<RedisConnectionFactory> redisConnectionFactory) {
113+
this.redisConnectionFactory = springSessionRedisConnectionFactory
114+
.getIfAvailable(redisConnectionFactory::getObject);
115+
}
116+
117+
protected RedisConnectionFactory getRedisConnectionFactory() {
118+
return this.redisConnectionFactory;
119+
}
120+
121+
@Autowired(required = false)
122+
@Qualifier("springSessionDefaultRedisSerializer")
123+
public void setDefaultRedisSerializer(RedisSerializer<Object> defaultRedisSerializer) {
124+
this.defaultRedisSerializer = defaultRedisSerializer;
125+
}
126+
127+
protected RedisSerializer<Object> getDefaultRedisSerializer() {
128+
return this.defaultRedisSerializer;
129+
}
130+
131+
@Autowired(required = false)
132+
public void setSessionRepositoryCustomizer(
133+
ObjectProvider<SessionRepositoryCustomizer<T>> sessionRepositoryCustomizers) {
134+
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
135+
}
136+
137+
protected List<SessionRepositoryCustomizer<T>> getSessionRepositoryCustomizers() {
138+
return this.sessionRepositoryCustomizers;
139+
}
140+
141+
@Override
142+
public void setBeanClassLoader(ClassLoader classLoader) {
143+
this.classLoader = classLoader;
144+
}
145+
146+
protected RedisTemplate<String, Object> createRedisTemplate() {
147+
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
148+
redisTemplate.setKeySerializer(new StringRedisSerializer());
149+
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
150+
if (getDefaultRedisSerializer() != null) {
151+
redisTemplate.setDefaultSerializer(getDefaultRedisSerializer());
152+
}
153+
redisTemplate.setConnectionFactory(getRedisConnectionFactory());
154+
redisTemplate.setBeanClassLoader(this.classLoader);
155+
redisTemplate.afterPropertiesSet();
156+
return redisTemplate;
157+
}
158+
159+
}

0 commit comments

Comments
 (0)