Add auto-configuration for Spring Session's reactive support

Closes gh-9850
pull/10824/merge
Andy Wilkinson 7 years ago
parent bdab4aa97e
commit a276356328

@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.session;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
@ -27,13 +28,20 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
/**
* General condition used with all session configuration classes.
* Base class for Servlet and reactive session conditions.
*
* @author Tommy Ludwig
* @author Stephane Nicoll
* @author Madhura Bhave
* @author Andy Wilkinson
*/
class SessionCondition extends SpringBootCondition {
class AbstractSessionCondition extends SpringBootCondition {
private final WebApplicationType webApplicationType;
protected AbstractSessionCondition(WebApplicationType webApplicationType) {
this.webApplicationType = webApplicationType;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
@ -41,8 +49,8 @@ class SessionCondition extends SpringBootCondition {
ConditionMessage.Builder message = ConditionMessage
.forCondition("Session Condition");
Environment environment = context.getEnvironment();
StoreType required = SessionStoreMappings
.getType(((AnnotationMetadata) metadata).getClassName());
StoreType required = SessionStoreMappings.getType(this.webApplicationType,
((AnnotationMetadata) metadata).getClassName());
if (!environment.containsProperty("spring.session.store-type")) {
return ConditionOutcome.match(message.didNotFind("property", "properties")
.items(ConditionMessage.Style.QUOTE, "spring.session.store-type"));

@ -41,7 +41,7 @@ import org.springframework.session.hazelcast.config.annotation.web.http.Hazelcas
@ConditionalOnClass(HazelcastSessionRepository.class)
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(HazelcastInstance.class)
@Conditional(SessionCondition.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(HazelcastSessionProperties.class)
class HazelcastSessionConfiguration {

@ -43,7 +43,7 @@ import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessi
@ConditionalOnClass({ JdbcTemplate.class, JdbcOperationsSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(DataSource.class)
@Conditional(SessionCondition.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(JdbcSessionProperties.class)
class JdbcSessionConfiguration {

@ -0,0 +1,59 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.data.mongo.config.annotation.web.reactive.ReactiveMongoWebSessionConfiguration;
/**
* Mongo-backed reactive session configuration.
*
* @author Andy Wilkinson
*/
@Configuration
@ConditionalOnClass(ReactiveMongoWebSessionConfiguration.class)
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@ConditionalOnBean(ReactiveMongoOperations.class)
@Conditional(ReactiveSessionCondition.class)
@EnableConfigurationProperties(MongoSessionProperties.class)
class MongoReactiveSessionConfiguration {
@Configuration
static class SpringBootReactiveMongoWebSessionConfiguration
extends ReactiveMongoWebSessionConfiguration {
@Autowired
public void customize(SessionProperties sessionProperties,
MongoSessionProperties mongoSessionProperties) {
Integer timeout = sessionProperties.getTimeout();
if (timeout != null) {
setMaxInactiveIntervalInSeconds(timeout);
}
setCollectionName(mongoSessionProperties.getCollectionName());
}
}
}

@ -37,7 +37,7 @@ import org.springframework.session.data.mongo.config.annotation.web.http.MongoHt
@ConditionalOnClass(MongoHttpSessionConfiguration.class)
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(MongoOperations.class)
@Conditional(SessionCondition.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(MongoSessionProperties.class)
class MongoSessionConfiguration {

@ -0,0 +1,35 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.ReactiveSessionRepository;
/**
* No-op session configuration used to disable Spring Session using the environment.
*
* @author Tommy Ludwig
* @author Andy Wilkinson
*/
@Configuration
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@Conditional(ReactiveSessionCondition.class)
class NoOpReactiveSessionConfiguration {
}

@ -28,7 +28,7 @@ import org.springframework.session.SessionRepository;
*/
@Configuration
@ConditionalOnMissingBean(SessionRepository.class)
@Conditional(SessionCondition.class)
@Conditional(ServletSessionCondition.class)
class NoOpSessionConfiguration {
}

@ -0,0 +1,32 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
import org.springframework.boot.WebApplicationType;
/**
* General condition used with all reactive session configuration classes.
*
* @author Andy Wilkinson
*/
class ReactiveSessionCondition extends AbstractSessionCondition {
ReactiveSessionCondition() {
super(WebApplicationType.REACTIVE);
}
}

@ -0,0 +1,65 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
import org.springframework.session.data.redis.config.annotation.web.server.RedisWebSessionConfiguration;
/**
* Redis-backed reactive session configuration.
*
* @author Andy Wilkinson
*/
@Configuration
@ConditionalOnClass({ ReactiveRedisConnectionFactory.class,
ReactiveRedisOperationsSessionRepository.class })
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@ConditionalOnBean(ReactiveRedisConnectionFactory.class)
@Conditional(ReactiveSessionCondition.class)
@EnableConfigurationProperties(RedisSessionProperties.class)
class RedisReactiveSessionConfiguration {
@Configuration
static class SpringBootRedisWebSessionConfiguration
extends RedisWebSessionConfiguration {
private SessionProperties sessionProperties;
@Autowired
public void customize(SessionProperties sessionProperties,
RedisSessionProperties redisSessionProperties) {
this.sessionProperties = sessionProperties;
Integer timeout = this.sessionProperties.getTimeout();
if (timeout != null) {
setMaxInactiveIntervalInSeconds(timeout);
}
setRedisNamespace(redisSessionProperties.getNamespace());
setRedisFlushMode(redisSessionProperties.getFlushMode());
}
}
}

@ -42,7 +42,7 @@ import org.springframework.session.data.redis.config.annotation.web.http.RedisHt
@ConditionalOnClass({ RedisTemplate.class, RedisOperationsSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@Conditional(SessionCondition.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(RedisSessionProperties.class)
class RedisSessionConfiguration {

@ -0,0 +1,35 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
import org.springframework.boot.WebApplicationType;
/**
* General condition used with all servlet session configuration classes.
*
* @author Tommy Ludwig
* @author Stephane Nicoll
* @author Madhura Bhave
* @author Andy Wilkinson
*/
class ServletSessionCondition extends AbstractSessionCondition {
ServletSessionCondition() {
super(WebApplicationType.SERVLET);
}
}

@ -17,11 +17,13 @@
package org.springframework.boot.autoconfigure.session;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -43,6 +45,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
@ -69,77 +72,116 @@ public class SessionAutoConfiguration {
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@Import({ SessionRepositoryConfiguration.class, SessionRepositoryValidator.class,
@Import({ ServletSessionRepositoryValidator.class,
SessionRepositoryFilterConfiguration.class })
static class ServletSessionConfiguration {
@Configuration
@ConditionalOnMissingBean(SessionRepository.class)
@Import({ ServletSessionRepositoryImplementationValidator.class,
ServletSessionConfigurationImportSelector.class })
static class ServletSessionRepositoryConfiguration {
}
}
@Configuration
@ConditionalOnWebApplication(type = Type.REACTIVE)
@Import(ReactiveSessionRepositoryValidator.class)
static class ReactiveSessionConfiguration {
}
@Configuration
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@Import({ ReactiveSessionRepositoryImplementationValidator.class,
ReactiveSessionConfigurationImportSelector.class })
static class ReactiveSessionRepositoryConfiguration {
@Configuration
@ConditionalOnMissingBean(SessionRepository.class)
@Import({ SessionRepositoryImplementationValidator.class,
SessionConfigurationImportSelector.class })
static class SessionRepositoryConfiguration {
}
}
/**
* {@link ImportSelector} to add {@link StoreType} configuration classes.
* {@link ImportSelector} base class to add {@link StoreType} configuration classes.
*/
static class SessionConfigurationImportSelector implements ImportSelector {
static abstract class SessionConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
protected final String[] selectImports(AnnotationMetadata importingClassMetadata,
WebApplicationType webApplicationType) {
List<String> imports = new ArrayList<>();
StoreType[] types = StoreType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = SessionStoreMappings.getConfigurationClass(types[i]);
imports.add(SessionStoreMappings.getConfigurationClass(webApplicationType,
types[i]));
}
return imports;
return imports.toArray(new String[imports.size()]);
}
}
/**
* Bean used to validate that only one supported implementation is available in the
* classpath when the store-type property is not set.
* {@link ImportSelector} to add {@link StoreType} configuration classes for reactive
* web applications.
*/
static class ReactiveSessionConfigurationImportSelector
extends SessionConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return super.selectImports(importingClassMetadata,
WebApplicationType.REACTIVE);
}
}
/**
* {@link ImportSelector} to add {@link StoreType} configuration classes for Servlet
* web applications.
*/
static class SessionRepositoryImplementationValidator {
static class ServletSessionConfigurationImportSelector
extends SessionConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return super.selectImports(importingClassMetadata,
WebApplicationType.SERVLET);
}
}
/**
* Base class for beans used to validate that only one supported implementation is
* available in the classpath when the store-type property is not set.
*/
static class AbstractSessionRepositoryImplementationValidator {
private final List<String> candidates;
private final ClassLoader classLoader;
private final SessionProperties sessionProperties;
SessionRepositoryImplementationValidator(ApplicationContext applicationContext,
SessionProperties sessionProperties) {
AbstractSessionRepositoryImplementationValidator(
ApplicationContext applicationContext,
SessionProperties sessionProperties, List<String> candidates) {
this.classLoader = applicationContext.getClassLoader();
this.sessionProperties = sessionProperties;
this.candidates = candidates;
}
@PostConstruct
public void checkAvailableImplementations() {
List<Class<?>> candidates = new ArrayList<>();
addCandidate(candidates,
"org.springframework.session.hazelcast.HazelcastSessionRepository");
addCandidate(candidates,
"org.springframework.session.jdbc.JdbcOperationsSessionRepository");
addCandidate(candidates,
"org.springframework.session.data.mongo.MongoOperationsSessionRepository");
addCandidate(candidates,
"org.springframework.session.data.redis.RedisOperationsSessionRepository");
List<Class<?>> availableCandidates = new ArrayList<>();
for (String candidate : this.candidates) {
addCandidateIfAvailable(availableCandidates, candidate);
}
StoreType storeType = this.sessionProperties.getStoreType();
if (candidates.size() > 1 && storeType == null) {
throw new NonUniqueSessionRepositoryException(candidates);
if (availableCandidates.size() > 1 && storeType == null) {
throw new NonUniqueSessionRepositoryException(availableCandidates);
}
}
private void addCandidate(List<Class<?>> candidates, String type) {
private void addCandidateIfAvailable(List<Class<?>> candidates, String type) {
try {
Class<?> candidate = this.classLoader.loadClass(type);
if (candidate != null) {
@ -153,17 +195,52 @@ public class SessionAutoConfiguration {
}
/**
* Bean used to validate that a {@link SessionRepository} exists and provide a
* meaningful message if that's not the case.
* Bean used to validate that only one supported implementation is available in the
* classpath when the store-type property is not set.
*/
static class ServletSessionRepositoryImplementationValidator
extends AbstractSessionRepositoryImplementationValidator {
ServletSessionRepositoryImplementationValidator(
ApplicationContext applicationContext,
SessionProperties sessionProperties) {
super(applicationContext, sessionProperties, Arrays.asList(
"org.springframework.session.hazelcast.HazelcastSessionRepository",
"org.springframework.session.jdbc.JdbcOperationsSessionRepository",
"org.springframework.session.data.mongo.MongoOperationsSessionRepository",
"org.springframework.session.data.redis.RedisOperationsSessionRepository"));
}
}
/**
* Bean used to validate that only one supported implementation is available in the
* classpath when the store-type property is not set.
*/
static class SessionRepositoryValidator {
static class ReactiveSessionRepositoryImplementationValidator
extends AbstractSessionRepositoryImplementationValidator {
private SessionProperties sessionProperties;
ReactiveSessionRepositoryImplementationValidator(
ApplicationContext applicationContext,
SessionProperties sessionProperties) {
super(applicationContext, sessionProperties, Arrays.asList(
"org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository",
"org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository"));
}
private ObjectProvider<SessionRepository<?>> sessionRepositoryProvider;
}
SessionRepositoryValidator(SessionProperties sessionProperties,
ObjectProvider<SessionRepository<?>> sessionRepositoryProvider) {
/**
* Base class for validating that a (reactive) session repository bean exists.
*/
static class AbstractSessionRepositoryValidator {
private final SessionProperties sessionProperties;
private final ObjectProvider<?> sessionRepositoryProvider;
protected AbstractSessionRepositoryValidator(SessionProperties sessionProperties,
ObjectProvider<?> sessionRepositoryProvider) {
this.sessionProperties = sessionProperties;
this.sessionRepositoryProvider = sessionRepositoryProvider;
}
@ -184,4 +261,32 @@ public class SessionAutoConfiguration {
}
/**
* Bean used to validate that a {@link SessionRepository} exists and provide a
* meaningful message if that's not the case.
*/
static class ServletSessionRepositoryValidator
extends AbstractSessionRepositoryValidator {
ServletSessionRepositoryValidator(SessionProperties sessionProperties,
ObjectProvider<SessionRepository<?>> sessionRepositoryProvider) {
super(sessionProperties, sessionRepositoryProvider);
}
}
/**
* Bean used to validate that a {@link SessionRepository} exists and provide a
* meaningful message if that's not the case.
*/
static class ReactiveSessionRepositoryValidator
extends AbstractSessionRepositoryValidator {
ReactiveSessionRepositoryValidator(SessionProperties sessionProperties,
ObjectProvider<ReactiveSessionRepository<?>> sessionRepositoryProvider) {
super(sessionProperties, sessionRepositoryProvider);
}
}
}

@ -20,6 +20,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.WebApplicationType;
import org.springframework.util.Assert;
/**
@ -30,32 +31,62 @@ import org.springframework.util.Assert;
*/
final class SessionStoreMappings {
private static final Map<StoreType, Class<?>> MAPPINGS;
private static final Map<StoreType, Map<WebApplicationType, Class<?>>> MAPPINGS;
static {
Map<StoreType, Class<?>> mappings = new HashMap<>();
mappings.put(StoreType.REDIS, RedisSessionConfiguration.class);
mappings.put(StoreType.MONGODB, MongoSessionConfiguration.class);
mappings.put(StoreType.JDBC, JdbcSessionConfiguration.class);
mappings.put(StoreType.HAZELCAST, HazelcastSessionConfiguration.class);
mappings.put(StoreType.NONE, NoOpSessionConfiguration.class);
Map<StoreType, Map<WebApplicationType, Class<?>>> mappings = new HashMap<>();
mappings.put(StoreType.REDIS, createMapping(RedisSessionConfiguration.class,
RedisReactiveSessionConfiguration.class));
mappings.put(StoreType.MONGODB, createMapping(MongoSessionConfiguration.class,
MongoReactiveSessionConfiguration.class));
mappings.put(StoreType.JDBC, createMapping(JdbcSessionConfiguration.class));
mappings.put(StoreType.HAZELCAST,
createMapping(HazelcastSessionConfiguration.class));
mappings.put(StoreType.NONE, createMapping(NoOpSessionConfiguration.class,
NoOpReactiveSessionConfiguration.class));
MAPPINGS = Collections.unmodifiableMap(mappings);
}
static Map<WebApplicationType, Class<?>> createMapping(
Class<?> servletConfiguration) {
return createMapping(servletConfiguration, null);
}
static Map<WebApplicationType, Class<?>> createMapping(Class<?> servletConfiguration,
Class<?> reactiveConfiguration) {
Map<WebApplicationType, Class<?>> mapping = new HashMap<>();
mapping.put(WebApplicationType.SERVLET, servletConfiguration);
if (reactiveConfiguration != null) {
mapping.put(WebApplicationType.REACTIVE, reactiveConfiguration);
}
return mapping;
}
private SessionStoreMappings() {
}
static String getConfigurationClass(StoreType sessionStoreType) {
Class<?> configurationClass = MAPPINGS.get(sessionStoreType);
Assert.state(configurationClass != null,
static String getConfigurationClass(WebApplicationType webApplicationType,
StoreType sessionStoreType) {
Map<WebApplicationType, Class<?>> configurationClasses = MAPPINGS
.get(sessionStoreType);
Assert.state(configurationClasses != null,
() -> "Unknown session store type " + sessionStoreType);
Class<?> configurationClass = configurationClasses.get(webApplicationType);
if (configurationClass == null) {
return null;
}
return configurationClass.getName();
}
static StoreType getType(String configurationClassName) {
for (Map.Entry<StoreType, Class<?>> entry : MAPPINGS.entrySet()) {
if (entry.getValue().getName().equals(configurationClassName)) {
return entry.getKey();
static StoreType getType(WebApplicationType webApplicationType,
String configurationClassName) {
for (Map.Entry<StoreType, Map<WebApplicationType, Class<?>>> storeEntry : MAPPINGS
.entrySet()) {
for (Map.Entry<WebApplicationType, Class<?>> entry : storeEntry.getValue()
.entrySet()) {
if (entry.getValue().getName().equals(configurationClassName)) {
return storeEntry.getKey();
}
}
}
throw new IllegalStateException(

@ -16,7 +16,6 @@
package org.springframework.boot.autoconfigure.web.reactive;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -30,14 +29,12 @@ import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.server.session.WebSessionManager;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link HttpHandler}.
*
* @author Brian Clozel
* @author Stephane Nicoll
* @author Andy Wilkinson
* @since 2.0.0
*/
@Configuration
@ -58,16 +55,9 @@ public class HttpHandlerAutoConfiguration {
}
@Bean
public HttpHandler httpHandler(
ObjectProvider<WebSessionManager> webSessionManagerProvider) {
WebHttpHandlerBuilder builder = WebHttpHandlerBuilder
.applicationContext(this.applicationContext);
WebSessionManager webSessionManager = webSessionManagerProvider
.getIfAvailable();
if (webSessionManager != null) {
builder.sessionManager(webSessionManager);
}
return builder.build();
public HttpHandler httpHandler() {
return WebHttpHandlerBuilder.applicationContext(this.applicationContext)
.build();
}
}

@ -17,7 +17,9 @@
package org.springframework.boot.autoconfigure.session;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.SessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
@ -42,4 +44,18 @@ public abstract class AbstractSessionAutoConfigurationTests {
.getPropertyValue("defaultMaxInactiveInterval");
}
protected <T extends ReactiveSessionRepository<?>> T validateSessionRepository(
AssertableReactiveWebApplicationContext context, Class<T> type) {
assertThat(context).hasSingleBean(ReactiveSessionRepository.class);
ReactiveSessionRepository<?> repository = context
.getBean(ReactiveSessionRepository.class);
assertThat(repository).as("Wrong session repository type").isInstanceOf(type);
return type.cast(repository);
}
protected Integer getSessionTimeout(ReactiveSessionRepository<?> sessionRepository) {
return (Integer) new DirectFieldAccessor(sessionRepository)
.getPropertyValue("defaultMaxInactiveInterval");
}
}

@ -0,0 +1,95 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
import org.springframework.boot.test.context.HideClassesClassLoader;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository;
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Mongo-specific tests for {@link SessionAutoConfiguration}.
*
* @author Andy Wilkinson
*/
public class ReactiveSessionAutoConfigurationMongoTests
extends AbstractSessionAutoConfigurationTests {
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@Test
public void defaultConfig() {
this.contextRunner.withPropertyValues("spring.session.store-type=mongodb")
.withConfiguration(AutoConfigurations.of(
EmbeddedMongoAutoConfiguration.class,
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class,
MongoReactiveDataAutoConfiguration.class))
.run(validateSpringSessionUsesMongo("sessions"));
}
@Test
public void defaultConfigWithUniqueStoreImplementation() {
this.contextRunner
.withClassLoader(new HideClassesClassLoader(
ReactiveRedisOperationsSessionRepository.class))
.withConfiguration(AutoConfigurations.of(
EmbeddedMongoAutoConfiguration.class,
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class,
MongoReactiveDataAutoConfiguration.class))
.run(validateSpringSessionUsesMongo("sessions"));
}
@Test
public void mongoSessionStoreWithCustomizations() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(
EmbeddedMongoAutoConfiguration.class,
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class,
MongoReactiveDataAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=mongodb",
"spring.session.mongodb.collection-name=foo")
.run(validateSpringSessionUsesMongo("foo"));
}
private ContextConsumer<AssertableReactiveWebApplicationContext> validateSpringSessionUsesMongo(
String collectionName) {
return (context) -> {
ReactiveMongoOperationsSessionRepository repository = validateSessionRepository(
context, ReactiveMongoOperationsSessionRepository.class);
assertThat(new DirectFieldAccessor(repository)
.getPropertyValue("collectionName")).isEqualTo(collectionName);
};
}
}

@ -0,0 +1,95 @@
/*
* Copyright 2012-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportMessage;
import org.springframework.boot.test.context.HideClassesClassLoader;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.testsupport.rule.RedisTestServer;
import org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository;
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
import org.springframework.session.data.redis.RedisFlushMode;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Reactive Redis-specific tests for {@link SessionAutoConfiguration}.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
*/
public class ReactiveSessionAutoConfigurationRedisTests
extends AbstractSessionAutoConfigurationTests {
@Rule
public final RedisTestServer redis = new RedisTestServer();
protected final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@Test
public void defaultConfig() {
this.contextRunner.withPropertyValues("spring.session.store-type=redis")
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class,
RedisReactiveAutoConfiguration.class))
.run(validateSpringSessionUsesRedis(RedisFlushMode.ON_SAVE));
}
@Test
public void defaultConfigWithUniqueStoreImplementation() {
this.contextRunner
.withClassLoader(new HideClassesClassLoader(
ReactiveMongoOperationsSessionRepository.class))
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class,
RedisReactiveAutoConfiguration.class))
.run(validateSpringSessionUsesRedis(RedisFlushMode.ON_SAVE));
}
@Test
public void redisSessionStoreWithCustomizations() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class,
RedisReactiveAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=redis",
"spring.session.redis.namespace=foo",
"spring.session.redis.flush-mode=immediate")
.run(validateSpringSessionUsesRedis(RedisFlushMode.IMMEDIATE));
}
private ContextConsumer<AssertableReactiveWebApplicationContext> validateSpringSessionUsesRedis(
RedisFlushMode flushMode) {
return (context) -> {
System.out.println(new ConditionEvaluationReportMessage(
context.getBean(ConditionEvaluationReport.class)));
ReactiveRedisOperationsSessionRepository repository = validateSessionRepository(
context, ReactiveRedisOperationsSessionRepository.class);
assertThat(new DirectFieldAccessor(repository)
.getPropertyValue("redisFlushMode")).isEqualTo(flushMode);
};
}
}

@ -157,7 +157,7 @@
<spring-retry.version>1.2.1.RELEASE</spring-retry.version>
<spring-security.version>5.0.0.BUILD-SNAPSHOT</spring-security.version>
<spring-session.version>2.0.0.BUILD-SNAPSHOT</spring-session.version>
<spring-session-data-mongodb.version>2.0.0.M4</spring-session-data-mongodb.version>
<spring-session-data-mongodb.version>2.0.0.BUILD-SNAPSHOT</spring-session-data-mongodb.version>
<spring-social.version>2.0.0.M4</spring-social.version>
<spring-social-facebook.version>3.0.0.M3</spring-social-facebook.version>
<spring-social-linkedin.version>2.0.0.M3</spring-social-linkedin.version>

@ -5506,13 +5506,19 @@ classes for more details.
[[boot-features-session]]
== Spring Session
Spring Boot provides Spring Session auto-configuration for a wide range of stores:
Spring Boot provides Spring Session auto-configuration for a wide range of stores. When
building a Servlet web application, the following stores can be auto-configured:
* JDBC
* Redis
* Hazelcast
* MongoDB
When building a reactive web applicaiton, the following stores can be auto-configured:
* Redis
* MongoDB
If Spring Session is available, you must choose the
{sc-spring-boot-autoconfigure}/session/StoreType.{sc-ext}[`StoreType`] that you wish to
use to store the sessions. For instance to use JDBC as backend store, you'd configure

Loading…
Cancel
Save