Simplify expired session cleanup jobs
At present, RedisIndexedHttpSessionConfiguration and JdbcHttpSessionConfiguration include [at]EnableScheduling annotated inner configuration classes that configure expired session cleanup jobs. This approach silently opts in users into general purpose task scheduling support provided by Spring Framework, which isn't something a library should do. Ideally, session cleanup jobs should only require a single thread dedicated to their execution and also one that doesn't compete for resources with general purpose task scheduling. This commit updates RedisIndexedSessionRepository and JdbcIndexedSessionRepository to have them manage their own ThreadPoolTaskScheduler for purposes of running expired session cleanup jobs. Closes gh-2136
This commit is contained in:
@@ -34,6 +34,8 @@ import java.util.stream.Collectors;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
@@ -48,6 +50,10 @@ import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
||||
import org.springframework.jdbc.support.lob.LobCreator;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.scheduling.support.CronExpression;
|
||||
import org.springframework.scheduling.support.CronTrigger;
|
||||
import org.springframework.session.DelegatingIndexResolver;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.FlushMode;
|
||||
@@ -130,14 +136,19 @@ import org.springframework.util.StringUtils;
|
||||
* @author Craig Andrews
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class JdbcIndexedSessionRepository
|
||||
implements FindByIndexNameSessionRepository<JdbcIndexedSessionRepository.JdbcSession> {
|
||||
public class JdbcIndexedSessionRepository implements
|
||||
FindByIndexNameSessionRepository<JdbcIndexedSessionRepository.JdbcSession>, InitializingBean, DisposableBean {
|
||||
|
||||
/**
|
||||
* The default name of database table used by Spring Session to store sessions.
|
||||
*/
|
||||
public static final String DEFAULT_TABLE_NAME = "SPRING_SESSION";
|
||||
|
||||
/**
|
||||
* The default cron expression used for expired session cleanup job.
|
||||
*/
|
||||
public static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
private static final String CREATE_SESSION_QUERY = """
|
||||
@@ -241,6 +252,10 @@ public class JdbcIndexedSessionRepository
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private String cleanupCron = DEFAULT_CLEANUP_CRON;
|
||||
|
||||
private ThreadPoolTaskScheduler taskScheduler;
|
||||
|
||||
/**
|
||||
* Create a new {@link JdbcIndexedSessionRepository} instance which uses the provided
|
||||
* {@link JdbcOperations} and {@link TransactionOperations} to manage sessions.
|
||||
@@ -255,6 +270,28 @@ public class JdbcIndexedSessionRepository
|
||||
prepareQueries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (!Scheduled.CRON_DISABLED.equals(this.cleanupCron)) {
|
||||
this.taskScheduler = createTaskScheduler();
|
||||
this.taskScheduler.initialize();
|
||||
this.taskScheduler.schedule(this::cleanUpExpiredSessions, new CronTrigger(this.cleanupCron));
|
||||
}
|
||||
}
|
||||
|
||||
private static ThreadPoolTaskScheduler createTaskScheduler() {
|
||||
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
|
||||
taskScheduler.setThreadNamePrefix("spring-session-");
|
||||
return taskScheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (this.taskScheduler != null) {
|
||||
this.taskScheduler.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of database table used to store sessions.
|
||||
* @param tableName the database table name
|
||||
@@ -397,6 +434,21 @@ public class JdbcIndexedSessionRepository
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cleanup cron expression.
|
||||
* @param cleanupCron the cleanup cron expression
|
||||
* @since 3.0.0
|
||||
* @see CronExpression
|
||||
* @see Scheduled#CRON_DISABLED
|
||||
*/
|
||||
public void setCleanupCron(String cleanupCron) {
|
||||
Assert.notNull(cleanupCron, "cleanupCron must not be null");
|
||||
if (!Scheduled.CRON_DISABLED.equals(cleanupCron)) {
|
||||
Assert.isTrue(CronExpression.isValidExpression(cleanupCron), "cleanupCron must be valid");
|
||||
}
|
||||
this.cleanupCron = cleanupCron;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcSession createSession() {
|
||||
MapSession delegate = new MapSession();
|
||||
|
||||
@@ -96,7 +96,7 @@ public @interface EnableJdbcHttpSession {
|
||||
* @return the session cleanup cron expression
|
||||
* @since 2.0.0
|
||||
*/
|
||||
String cleanupCron() default JdbcHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
|
||||
String cleanupCron() default JdbcIndexedSessionRepository.DEFAULT_CLEANUP_CRON;
|
||||
|
||||
/**
|
||||
* Flush mode for the sessions. The default is {@code ON_SAVE} which only updates the
|
||||
|
||||
@@ -43,9 +43,6 @@ import org.springframework.jdbc.support.MetaDataAccessException;
|
||||
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
|
||||
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
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.IndexResolver;
|
||||
import org.springframework.session.MapSession;
|
||||
@@ -80,13 +77,11 @@ import org.springframework.util.StringValueResolver;
|
||||
public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware {
|
||||
|
||||
static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
|
||||
|
||||
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
|
||||
|
||||
private String tableName = JdbcIndexedSessionRepository.DEFAULT_TABLE_NAME;
|
||||
|
||||
private String cleanupCron = DEFAULT_CLEANUP_CRON;
|
||||
private String cleanupCron = JdbcIndexedSessionRepository.DEFAULT_CLEANUP_CRON;
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
@@ -126,6 +121,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||
sessionRepository.setFlushMode(this.flushMode);
|
||||
sessionRepository.setSaveMode(this.saveMode);
|
||||
sessionRepository.setCleanupCron(this.cleanupCron);
|
||||
if (this.indexResolver != null) {
|
||||
sessionRepository.setIndexResolver(this.indexResolver);
|
||||
}
|
||||
@@ -281,25 +277,4 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration of scheduled job for cleaning up expired sessions.
|
||||
*/
|
||||
@EnableScheduling
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class SessionCleanupConfiguration implements SchedulingConfigurer {
|
||||
|
||||
private final JdbcIndexedSessionRepository sessionRepository;
|
||||
|
||||
SessionCleanupConfiguration(JdbcIndexedSessionRepository sessionRepository) {
|
||||
this.sessionRepository = sessionRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.addCronTask(this.sessionRepository::cleanUpExpiredSessions,
|
||||
JdbcHttpSessionConfiguration.this.cleanupCron);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.jdbc.core.PreparedStatementSetter;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
||||
import org.springframework.jdbc.support.lob.TemporaryLobCreator;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
@@ -243,6 +244,25 @@ class JdbcIndexedSessionRepositoryTests {
|
||||
.withMessage("saveMode must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setCleanupCronNull() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCleanupCron(null))
|
||||
.withMessage("cleanupCron must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setCleanupCronInvalid() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCleanupCron("test"))
|
||||
.withMessage("cleanupCron must be valid");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setCleanupCronDisabled() {
|
||||
this.repository.setCleanupCron(Scheduled.CRON_DISABLED);
|
||||
this.repository.afterPropertiesSet();
|
||||
assertThat(this.repository).extracting("taskScheduler").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSessionDefaultMaxInactiveInterval() {
|
||||
JdbcSession session = this.repository.createSession();
|
||||
|
||||
@@ -136,18 +136,16 @@ class JdbcHttpSessionConfigurationTests {
|
||||
void customCleanupCronAnnotation() {
|
||||
registerAndRefresh(DataSourceConfiguration.class, CustomCleanupCronExpressionAnnotationConfiguration.class);
|
||||
|
||||
JdbcHttpSessionConfiguration configuration = this.context.getBean(JdbcHttpSessionConfiguration.class);
|
||||
assertThat(configuration).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
|
||||
assertThat(repository).extracting("cleanupCron").isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customCleanupCronSetter() {
|
||||
registerAndRefresh(DataSourceConfiguration.class, CustomCleanupCronExpressionSetterConfiguration.class);
|
||||
|
||||
JdbcHttpSessionConfiguration configuration = this.context.getBean(JdbcHttpSessionConfiguration.class);
|
||||
assertThat(configuration).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
|
||||
assertThat(repository).extracting("cleanupCron").isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user