Add common flush mode support

Resolves: #1465
This commit is contained in:
Vedran Pavic
2019-06-21 15:49:59 +02:00
parent a6f6042831
commit 07b9433540
17 changed files with 309 additions and 53 deletions

View File

@@ -21,10 +21,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.FlushMode;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.redis.AbstractRedisITests;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -50,7 +50,7 @@ class RedisOperationsSessionRepositoryFlushImmediatelyITests<S extends Session>
}
@Configuration
@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE)
@EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE)
static class RedisHttpSessionConfig extends BaseConfig {
}

View File

@@ -16,6 +16,7 @@
package org.springframework.session.data.redis;
import org.springframework.session.FlushMode;
import org.springframework.session.SessionRepository;
/**
@@ -23,7 +24,9 @@ import org.springframework.session.SessionRepository;
*
* @author Rob Winch
* @since 1.1
* @deprecated since 2.2.0 in favor of {@link FlushMode}
*/
@Deprecated
public enum RedisFlushMode {
/**
@@ -31,7 +34,7 @@ public enum RedisFlushMode {
* {@link SessionRepository#save(org.springframework.session.Session)} is invoked. In
* a web environment this is typically done as soon as the HTTP response is committed.
*/
ON_SAVE,
ON_SAVE(FlushMode.ON_SAVE),
/**
* Writes to Redis as soon as possible. For example
@@ -39,6 +42,16 @@ public enum RedisFlushMode {
* example is that setting an attribute on the session will also write to Redis
* immediately.
*/
IMMEDIATE
IMMEDIATE(FlushMode.IMMEDIATE);
private final FlushMode flushMode;
RedisFlushMode(FlushMode flushMode) {
this.flushMode = flushMode;
}
public FlushMode getFlushMode() {
return this.flushMode;
}
}

View File

@@ -38,6 +38,7 @@ import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.DelegatingIndexResolver;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.FlushMode;
import org.springframework.session.IndexResolver;
import org.springframework.session.MapSession;
import org.springframework.session.PrincipalNameIndexResolver;
@@ -300,7 +301,7 @@ public class RedisOperationsSessionRepository
private RedisSerializer<Object> defaultSerializer = new JdkSerializationRedisSerializer();
private RedisFlushMode redisFlushMode = RedisFlushMode.ON_SAVE;
private FlushMode flushMode = FlushMode.ON_SAVE;
/**
* Creates a new instance. For an example, refer to the class level javadoc.
@@ -352,10 +353,21 @@ public class RedisOperationsSessionRepository
/**
* Sets the redis flush mode. Default flush mode is {@link RedisFlushMode#ON_SAVE}.
* @param redisFlushMode the new redis flush mode
* @deprecated since 2.2.0 in favor of {@link #setFlushMode(FlushMode)}
*/
@Deprecated
public void setRedisFlushMode(RedisFlushMode redisFlushMode) {
Assert.notNull(redisFlushMode, "redisFlushMode cannot be null");
this.redisFlushMode = redisFlushMode;
setFlushMode(redisFlushMode.getFlushMode());
}
/**
* Sets the redis flush mode. Default flush mode is {@link FlushMode#ON_SAVE}.
* @param flushMode the flush mode
*/
public void setFlushMode(FlushMode flushMode) {
Assert.notNull(flushMode, "flushMode cannot be null");
this.flushMode = flushMode;
}
/**
@@ -770,7 +782,7 @@ public class RedisOperationsSessionRepository
}
private void flushImmediateIfNecessary() {
if (RedisOperationsSessionRepository.this.redisFlushMode == RedisFlushMode.IMMEDIATE) {
if (RedisOperationsSessionRepository.this.flushMode == FlushMode.IMMEDIATE) {
save();
}
}

View File

@@ -24,6 +24,7 @@ import java.util.Map;
import java.util.Set;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
@@ -49,7 +50,7 @@ public class SimpleRedisOperationsSessionRepository
private String keyNamespace = DEFAULT_KEY_NAMESPACE;
private RedisFlushMode flushMode = RedisFlushMode.ON_SAVE;
private FlushMode flushMode = FlushMode.ON_SAVE;
/**
* Create a new {@link SimpleRedisOperationsSessionRepository} instance.
@@ -83,7 +84,7 @@ public class SimpleRedisOperationsSessionRepository
* Set the flush mode.
* @param flushMode the flush mode
*/
public void setFlushMode(RedisFlushMode flushMode) {
public void setFlushMode(FlushMode flushMode) {
Assert.notNull(flushMode, "flushMode must not be null");
this.flushMode = flushMode;
}
@@ -231,7 +232,7 @@ public class SimpleRedisOperationsSessionRepository
}
private void flushIfRequired() {
if (SimpleRedisOperationsSessionRepository.this.flushMode == RedisFlushMode.IMMEDIATE) {
if (SimpleRedisOperationsSessionRepository.this.flushMode == FlushMode.IMMEDIATE) {
save();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2014-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.
@@ -25,6 +25,7 @@ import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
@@ -94,9 +95,23 @@ public @interface EnableRedisHttpSession {
* Session are immediately written to the Redis instance.
* @return the {@link RedisFlushMode} to use
* @since 1.1
* @deprecated since 2.2.0 in favor of {@link #flushMode()}
*/
@Deprecated
RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
/**
* Flush mode for the Redis sessions. The default is {@code ON_SAVE} which only
* updates the backing Redis when {@link SessionRepository#save(Session)} is invoked.
* In a web environment this happens just before the HTTP response is committed.
* <p>
* Setting the value to {@code IMMEDIATE} will ensure that the any updates to the
* Session are immediately written to the Redis instance.
* @return the {@link FlushMode} to use
* @since 2.2.0
*/
FlushMode flushMode() default FlushMode.ON_SAVE;
/**
* The cron expression for expired session cleanup job. By default runs every minute.
* @return the session cleanup cron expression

View File

@@ -48,6 +48,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.data.redis.RedisFlushMode;
@@ -83,7 +84,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
private String redisNamespace = RedisOperationsSessionRepository.DEFAULT_NAMESPACE;
private RedisFlushMode redisFlushMode = RedisFlushMode.ON_SAVE;
private FlushMode flushMode = FlushMode.ON_SAVE;
private String cleanupCron = DEFAULT_CLEANUP_CRON;
@@ -115,7 +116,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
if (StringUtils.hasText(this.redisNamespace)) {
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setRedisFlushMode(this.redisFlushMode);
sessionRepository.setFlushMode(this.flushMode);
int database = resolveDatabase();
sessionRepository.setDatabase(database);
return sessionRepository;
@@ -153,9 +154,15 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
this.redisNamespace = namespace;
}
@Deprecated
public void setRedisFlushMode(RedisFlushMode redisFlushMode) {
Assert.notNull(redisFlushMode, "redisFlushMode cannot be null");
this.redisFlushMode = redisFlushMode;
setFlushMode(redisFlushMode.getFlushMode());
}
public void setFlushMode(FlushMode flushMode) {
Assert.notNull(flushMode, "flushMode cannot be null");
this.flushMode = flushMode;
}
public void setCleanupCron(String cleanupCron) {
@@ -217,6 +224,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
}
@Override
@SuppressWarnings("deprecation")
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> attributeMap = importMetadata
.getAnnotationAttributes(EnableRedisHttpSession.class.getName());
@@ -226,7 +234,12 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
if (StringUtils.hasText(redisNamespaceValue)) {
this.redisNamespace = this.embeddedValueResolver.resolveStringValue(redisNamespaceValue);
}
this.redisFlushMode = attributes.getEnum("redisFlushMode");
FlushMode flushMode = attributes.getEnum("flushMode");
RedisFlushMode redisFlushMode = attributes.getEnum("redisFlushMode");
if (flushMode == FlushMode.ON_SAVE && redisFlushMode != RedisFlushMode.ON_SAVE) {
flushMode = redisFlushMode.getFlushMode();
}
this.flushMode = flushMode;
String cleanupCron = attributes.getString("cleanupCron");
if (StringUtils.hasText(cleanupCron)) {
this.cleanupCron = cleanupCron;

View File

@@ -46,6 +46,7 @@ import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession;
@@ -636,7 +637,7 @@ class RedisOperationsSessionRepositoryTests {
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
this.redisRepository.setFlushMode(FlushMode.IMMEDIATE);
RedisSession session = this.redisRepository.createSession();
Map<String, Object> delta = getDelta();
@@ -655,7 +656,7 @@ class RedisOperationsSessionRepositoryTests {
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
this.redisRepository.setDefaultMaxInactiveInterval(60);
this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
this.redisRepository.setFlushMode(FlushMode.IMMEDIATE);
this.redisRepository.createSession();
Map<String, Object> delta = getDelta();
assertThat(delta.size()).isEqualTo(3);
@@ -668,7 +669,7 @@ class RedisOperationsSessionRepositoryTests {
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
this.redisRepository.setFlushMode(FlushMode.IMMEDIATE);
RedisSession session = this.redisRepository.createSession();
String attrName = "someAttribute";
session.setAttribute(attrName, "someValue");
@@ -685,7 +686,7 @@ class RedisOperationsSessionRepositoryTests {
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
this.redisRepository.setFlushMode(FlushMode.IMMEDIATE);
RedisSession session = this.redisRepository.createSession();
String attrName = "someAttribute";
session.removeAttribute(attrName);
@@ -702,7 +703,7 @@ class RedisOperationsSessionRepositoryTests {
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
this.redisRepository.setFlushMode(FlushMode.IMMEDIATE);
RedisSession session = this.redisRepository.createSession();
reset(this.boundHashOperations);
@@ -718,7 +719,7 @@ class RedisOperationsSessionRepositoryTests {
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
this.redisRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
this.redisRepository.setFlushMode(FlushMode.IMMEDIATE);
RedisSession session = this.redisRepository.createSession();
session.setLastAccessedTime(Instant.now());
@@ -729,6 +730,12 @@ class RedisOperationsSessionRepositoryTests {
map(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, session.getLastAccessedTime().toEpochMilli()));
}
@Test
void setFlushModeNull() {
assertThatIllegalArgumentException().isThrownBy(() -> this.redisRepository.setFlushMode(null))
.withMessage("flushMode cannot be null");
}
@Test
void setRedisFlushModeNull() {
assertThatIllegalArgumentException().isThrownBy(() -> this.redisRepository.setRedisFlushMode(null))

View File

@@ -33,6 +33,7 @@ import org.mockito.MockitoAnnotations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
import org.springframework.session.data.redis.SimpleRedisOperationsSessionRepository.RedisSession;
import org.springframework.test.util.ReflectionTestUtils;
@@ -114,9 +115,8 @@ class SimpleRedisOperationsSessionRepositoryTests {
@Test
void setFlushMode_ValidFlushMode_ShouldSetFlushMode() {
this.sessionRepository.setFlushMode(RedisFlushMode.IMMEDIATE);
assertThat(ReflectionTestUtils.getField(this.sessionRepository, "flushMode"))
.isEqualTo(RedisFlushMode.IMMEDIATE);
this.sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
assertThat(ReflectionTestUtils.getField(this.sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
}
@Test
@@ -145,7 +145,7 @@ class SimpleRedisOperationsSessionRepositoryTests {
@Test
void createSession_ImmediateFlushMode_ShouldCreateSession() {
this.sessionRepository.setFlushMode(RedisFlushMode.IMMEDIATE);
this.sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
RedisSession session = this.sessionRepository.createSession();
String key = getSessionKey(session.getId());
verify(this.sessionRedisOperations).opsForHash();

View File

@@ -34,6 +34,8 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.session.FlushMode;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
import org.springframework.test.util.ReflectionTestUtils;
@@ -86,6 +88,42 @@ class RedisHttpSessionConfigurationTests {
assertThat(ReflectionTestUtils.getField(configuration, "redisNamespace")).isEqualTo("customRedisNamespace");
}
@Test
void customFlushImmediately() {
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyConfiguration.class);
RedisOperationsSessionRepository sessionRepository = this.context
.getBean(RedisOperationsSessionRepository.class);
assertThat(sessionRepository).isNotNull();
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
}
@Test
void customFlushImmediatelyLegacy() {
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelyLegacyConfiguration.class);
RedisOperationsSessionRepository sessionRepository = this.context
.getBean(RedisOperationsSessionRepository.class);
assertThat(sessionRepository).isNotNull();
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
}
@Test
void setCustomFlushImmediately() {
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetConfiguration.class);
RedisOperationsSessionRepository sessionRepository = this.context
.getBean(RedisOperationsSessionRepository.class);
assertThat(sessionRepository).isNotNull();
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
}
@Test
void setCustomFlushImmediatelyLegacy() {
registerAndRefresh(RedisConfig.class, CustomFlushImmediatelySetLegacyConfiguration.class);
RedisOperationsSessionRepository sessionRepository = this.context
.getBean(RedisOperationsSessionRepository.class);
assertThat(sessionRepository).isNotNull();
assertThat(ReflectionTestUtils.getField(sessionRepository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
}
@Test
void customCleanupCronAnnotation() {
registerAndRefresh(RedisConfig.class, CustomCleanupCronExpressionAnnotationConfiguration.class);
@@ -217,6 +255,36 @@ class RedisHttpSessionConfigurationTests {
}
@Configuration
static class CustomFlushImmediatelySetConfiguration extends RedisHttpSessionConfiguration {
CustomFlushImmediatelySetConfiguration() {
setFlushMode(FlushMode.IMMEDIATE);
}
}
@Configuration
static class CustomFlushImmediatelySetLegacyConfiguration extends RedisHttpSessionConfiguration {
CustomFlushImmediatelySetLegacyConfiguration() {
setRedisFlushMode(RedisFlushMode.IMMEDIATE);
}
}
@Configuration
@EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE)
static class CustomFlushImmediatelyConfiguration {
}
@Configuration
@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE)
static class CustomFlushImmediatelyLegacyConfiguration {
}
@EnableRedisHttpSession(cleanupCron = CLEANUP_CRON_EXPRESSION)
static class CustomCleanupCronExpressionAnnotationConfiguration {