From de007840a8908f3089f0644ef0a6e93c8a7e56fc Mon Sep 17 00:00:00 2001 From: Tommy Ludwig Date: Tue, 26 Jan 2016 22:29:30 +0900 Subject: [PATCH 1/3] Spring Session auto-configuration expansion This implementation was inspired in large part by the cache auto-configuration. In addition to the originally supported Redis, now Hazelcast, an in-memory map, as well as a no-op option are supported. It should be easy to extend this to include additional data stores in the future. Closes gh-3811 --- .../HazelcastSessionConfiguration.java | 62 ++++++++++++++++ .../session/NoOpSessionConfiguration.java | 35 ++++++++++ .../session/RedisSessionConfiguration.java | 63 +++++++++++++++++ .../session/SessionAutoConfiguration.java | 70 +++++++++---------- .../session/SessionCondition.java | 50 +++++++++++++ .../session/SessionProperties.java | 57 +++++++++++++++ .../session/SessionStoreMappings.java | 62 ++++++++++++++++ .../session/SessionStoreType.java | 47 +++++++++++++ .../session/SimpleSessionConfiguration.java | 51 ++++++++++++++ 9 files changed, 459 insertions(+), 38 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreType.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java new file mode 100644 index 0000000000..a59310fabe --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2016 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 javax.annotation.PostConstruct; + +import com.hazelcast.core.HazelcastInstance; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.SessionRepository; +import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession; +import org.springframework.session.hazelcast.config.annotation.web.http.HazelcastHttpSessionConfiguration; + +/** + * Hazelcast backed session auto-configuration. + * + * @author Tommy Ludwig + * @since 1.4.0 + */ +@Configuration +@ConditionalOnClass({ HazelcastInstance.class, HazelcastHttpSessionConfiguration.class }) +@ConditionalOnMissingBean({ SessionRepository.class, HazelcastHttpSessionConfiguration.class }) +@EnableHazelcastHttpSession +@Conditional(SessionCondition.class) +class HazelcastSessionConfiguration { + + private final ServerProperties serverProperties; + + private final MapSessionRepository sessionRepository; + + HazelcastSessionConfiguration(ServerProperties serverProperties, MapSessionRepository sessionRepository) { + this.serverProperties = serverProperties; + this.sessionRepository = sessionRepository; + } + + @PostConstruct + public void applyConfigurationProperties() { + Integer timeout = this.serverProperties.getSession().getTimeout(); + if (timeout != null) { + this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + } + } +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java new file mode 100644 index 0000000000..581ac958c0 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2016 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.SessionRepository; + +/** + * No-op session configuration used to disable Spring Session auto configuration via the + * environment. + * + * @author Tommy Ludwig + * @since 1.4.0 + */ +@Configuration +@ConditionalOnMissingBean(SessionRepository.class) +@Conditional(SessionCondition.class) +class NoOpSessionConfiguration { +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java new file mode 100644 index 0000000000..402f2ed05e --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2016 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 javax.annotation.PostConstruct; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.session.SessionRepository; +import org.springframework.session.data.redis.RedisOperationsSessionRepository; +import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; +import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration; + +/** + * Redis backed session auto-configuration. + * + * @author Andy Wilkinson + * @author Tommy Ludwig + * @since 1.4.0 + */ +@Configuration +@ConditionalOnClass(RedisConnectionFactory.class) +@ConditionalOnMissingBean({ SessionRepository.class, RedisHttpSessionConfiguration.class }) +@EnableRedisHttpSession +@Conditional(SessionCondition.class) +class RedisSessionConfiguration { + + private final ServerProperties serverProperties; + + private final RedisOperationsSessionRepository sessionRepository; + + RedisSessionConfiguration(ServerProperties serverProperties, RedisOperationsSessionRepository sessionRepository) { + this.serverProperties = serverProperties; + this.sessionRepository = sessionRepository; + } + + @PostConstruct + public void applyConfigurationProperties() { + Integer timeout = this.serverProperties.getSession().getTimeout(); + if (timeout != null) { + this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + } + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java index b26de37a33..a3cb962a98 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java @@ -23,64 +23,58 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.SessionConfigurationImportSelector; import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.session.Session; -import org.springframework.session.data.redis.RedisOperationsSessionRepository; -import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; -import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration; +import org.springframework.session.SessionRepository; /** * {@link EnableAutoConfiguration Auto-configuration} for Spring Session. * * @author Andy Wilkinson + * @author Tommy Ludwig * @since 1.3.0 */ @Configuration @ConditionalOnClass(Session.class) -@AutoConfigureAfter(RedisAutoConfiguration.class) +@ConditionalOnWebApplication +@ConditionalOnMissingBean(SessionRepository.class) +@AutoConfigureAfter({ HazelcastAutoConfiguration.class, RedisAutoConfiguration.class }) +@Import(SessionConfigurationImportSelector.class) public class SessionAutoConfiguration { - @EnableConfigurationProperties - @ConditionalOnClass(RedisConnectionFactory.class) - @ConditionalOnWebApplication - @ConditionalOnMissingBean(RedisHttpSessionConfiguration.class) - @EnableRedisHttpSession @Configuration - public static class SessionRedisHttpConfiguration { + @ConditionalOnMissingBean(value = ServerProperties.class, search = SearchStrategy.CURRENT) + // Just in case user switches off ServerPropertiesAutoConfiguration + public static class ServerPropertiesConfiguration { - private final ServerProperties serverProperties; - - private final RedisOperationsSessionRepository sessionRepository; - - public SessionRedisHttpConfiguration(ServerProperties serverProperties, - RedisOperationsSessionRepository sessionRepository) { - this.serverProperties = serverProperties; - this.sessionRepository = sessionRepository; - applyConfigurationProperties(); + @Bean + // Use the same bean name as the default one for any old webapp + public ServerProperties serverProperties() { + return new ServerProperties(); } - private void applyConfigurationProperties() { - Integer timeout = this.serverProperties.getSession().getTimeout(); - if (timeout != null) { - this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + } + + /** + * {@link ImportSelector} to add {@link SessionStoreType} configuration classes. + */ + static class SessionConfigurationImportSelector implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + SessionStoreType[] types = SessionStoreType.values(); + String[] imports = new String[types.length]; + for (int i = 0; i < types.length; i++) { + imports[i] = SessionStoreMappings.getConfigurationClass(types[i]); } - } - - @Configuration - @ConditionalOnMissingBean(value = ServerProperties.class, search = SearchStrategy.CURRENT) - // Just in case user switches off ServerPropertiesAutoConfiguration - public static class ServerPropertiesConfiguration { - - @Bean - // Use the same bean name as the default one for any old webapp - public ServerProperties serverProperties() { - return new ServerProperties(); - } - + return imports; } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java new file mode 100644 index 0000000000..a3a3261a8d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2016 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.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.AnnotationMetadata; + +/** + * General condition used by all session auto-configuration classes. + * + * @author Tommy Ludwig + */ +class SessionCondition extends SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( + context.getEnvironment(), "spring.session.store."); + if (!resolver.containsProperty("type")) { + return ConditionOutcome.match("Automatic session store type"); + } + SessionStoreType sessionStoreType = SessionStoreMappings + .getType(((AnnotationMetadata) metadata).getClassName()); + String value = resolver.getProperty("type").replace("-", "_").toUpperCase(); + if (value.equals(sessionStoreType.name())) { + return ConditionOutcome.match("Session store type " + sessionStoreType); + } + return ConditionOutcome.noMatch("Session store type " + value); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java new file mode 100644 index 0000000000..66b5eea50d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2016 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.context.properties.ConfigurationProperties; + +/** + * Properties for configuring Spring Session's auto-configuration. + * + * @author Tommy Ludwig + * @since 1.4.0 + */ +@ConfigurationProperties("spring.session") +public class SessionProperties { + + private Store store; + + public Store getStore() { + return this.store; + } + + public void setStore(Store store) { + this.store = store; + } + + /** + * Session store-specific properties. + */ + public static class Store { + /** + * Session data store type, auto-detected according to the environment by default. + */ + private SessionStoreType type; + + public SessionStoreType getType() { + return this.type; + } + + public void setType(SessionStoreType type) { + this.type = type; + } + } +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java new file mode 100644 index 0000000000..50078464af --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2016 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 java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * Mappings between {@link SessionStoreType} and {@code @Configuration}. + * + * @author Tommy Ludwig + */ +final class SessionStoreMappings { + + private SessionStoreMappings() { + } + + private static final Map> MAPPINGS; + + static { + Map> mappings = new HashMap>(); + mappings.put(SessionStoreType.REDIS, RedisSessionConfiguration.class); + mappings.put(SessionStoreType.HAZELCAST, HazelcastSessionConfiguration.class); + mappings.put(SessionStoreType.SIMPLE, SimpleSessionConfiguration.class); + mappings.put(SessionStoreType.NONE, NoOpSessionConfiguration.class); + MAPPINGS = Collections.unmodifiableMap(mappings); + } + + public static String getConfigurationClass(SessionStoreType sessionStoreType) { + Class configurationClass = MAPPINGS.get(sessionStoreType); + Assert.state(configurationClass != null, + "Unknown session store type " + sessionStoreType); + return configurationClass.getName(); + } + + public static SessionStoreType getType(String configurationClassName) { + for (Map.Entry> entry : MAPPINGS.entrySet()) { + if (entry.getValue().getName().equals(configurationClassName)) { + return entry.getKey(); + } + } + throw new IllegalStateException( + "Unknown configuration class " + configurationClassName); + } +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreType.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreType.java new file mode 100644 index 0000000000..ce508c5896 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreType.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2016 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; + +/** + * Supported Spring Session data store types. + * + * @author Tommy Ludwig + * @since 1.4.0 + */ +public enum SessionStoreType { + + /** + * Redis backed sessions. + */ + REDIS, + + /** + * Hazelcast backed sessions. + */ + HAZELCAST, + + /** + * Simple in-memory map of sessions. + */ + SIMPLE, + + /** + * No session datastore. + */ + NONE; + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java new file mode 100644 index 0000000000..4334782787 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2016 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.boot.autoconfigure.web.ServerProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.ExpiringSession; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.SessionRepository; +import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; + +/** + * In-memory session configuration, intended as a fallback. + * + * @author Tommy Ludwig + * @since 1.4.0 + */ +@Configuration +@ConditionalOnMissingBean(SessionRepository.class) +@EnableSpringHttpSession +@Conditional(SessionCondition.class) +class SimpleSessionConfiguration { + + @Bean + public SessionRepository sessionRepository(ServerProperties serverProperties) { + MapSessionRepository sessionRepository = new MapSessionRepository(); + + Integer timeout = serverProperties.getSession().getTimeout(); + if (serverProperties.getSession().getTimeout() != null) { + sessionRepository.setDefaultMaxInactiveInterval(timeout); + } + return sessionRepository; + } +} From 0be00e2a6d1575e33e9486a3f2118c8a870298a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Tue, 5 Apr 2016 19:36:42 +1000 Subject: [PATCH 2/3] Polish session auto-configuration See gh-5158 --- .../HazelcastSessionConfiguration.java | 13 +- .../session/JdbcSessionConfiguration.java | 59 +++++++++ .../session/MongoSessionConfiguration.java | 58 +++++++++ .../session/RedisSessionConfiguration.java | 15 +-- .../session/SessionAutoConfiguration.java | 12 +- .../session/SessionCondition.java | 2 +- .../session/SessionProperties.java | 6 +- .../session/SessionStoreMappings.java | 34 ++--- .../session/SimpleSessionConfiguration.java | 2 - .../{SessionStoreType.java => StoreType.java} | 15 ++- .../autoconfigure/session/package-info.java | 20 +++ .../session/StoreTypesConfigurationTests.java | 120 ++++++++++++++++++ .../web/FilterOrderingIntegrationTests.java | 3 + .../appendix-application-properties.adoc | 3 + .../main/asciidoc/spring-boot-features.adoc | 16 ++- 15 files changed, 332 insertions(+), 46 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java rename spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/{SessionStoreType.java => StoreType.java} (86%) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/package-info.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java index a59310fabe..95f2f15587 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java @@ -20,32 +20,29 @@ import javax.annotation.PostConstruct; import com.hazelcast.core.HazelcastInstance; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.session.MapSessionRepository; -import org.springframework.session.SessionRepository; import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession; -import org.springframework.session.hazelcast.config.annotation.web.http.HazelcastHttpSessionConfiguration; /** * Hazelcast backed session auto-configuration. * * @author Tommy Ludwig + * @author Eddú Meléndez * @since 1.4.0 */ @Configuration -@ConditionalOnClass({ HazelcastInstance.class, HazelcastHttpSessionConfiguration.class }) -@ConditionalOnMissingBean({ SessionRepository.class, HazelcastHttpSessionConfiguration.class }) +@ConditionalOnBean({ HazelcastInstance.class }) @EnableHazelcastHttpSession @Conditional(SessionCondition.class) class HazelcastSessionConfiguration { - private final ServerProperties serverProperties; + private ServerProperties serverProperties; - private final MapSessionRepository sessionRepository; + private MapSessionRepository sessionRepository; HazelcastSessionConfiguration(ServerProperties serverProperties, MapSessionRepository sessionRepository) { this.serverProperties = serverProperties; diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java new file mode 100644 index 0000000000..7a4c8f7fad --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2016 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 javax.annotation.PostConstruct; +import javax.sql.DataSource; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.jdbc.JdbcOperationsSessionRepository; +import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; + +/** + * JDBC backed session auto-configuration. + * + * @author Eddú Meléndez + * @since 1.4.0 + */ +@Configuration +@ConditionalOnBean(DataSource.class) +@EnableJdbcHttpSession +@Conditional(SessionCondition.class) +class JdbcSessionConfiguration { + + private ServerProperties serverProperties; + + private JdbcOperationsSessionRepository sessionRepository; + + JdbcSessionConfiguration(ServerProperties serverProperties, + JdbcOperationsSessionRepository sessionRepository) { + this.serverProperties = serverProperties; + this.sessionRepository = sessionRepository; + } + + @PostConstruct + public void applyConfigurationProperties() { + Integer timeout = this.serverProperties.getSession().getTimeout(); + if (timeout != null) { + this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + } + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java new file mode 100644 index 0000000000..1eb6f9d86c --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2016 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 javax.annotation.PostConstruct; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.session.data.mongo.MongoOperationsSessionRepository; +import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession; + +/** + * Mongo backed session auto-configuration. + * + * @author Eddú Meléndez + * @since 1.4.0 + */ +@Configuration +@ConditionalOnBean(MongoOperations.class) +@EnableMongoHttpSession +@Conditional(SessionCondition.class) +class MongoSessionConfiguration { + + private ServerProperties serverProperties; + + private MongoOperationsSessionRepository sessionRepository; + + MongoSessionConfiguration(ServerProperties serverProperties, MongoOperationsSessionRepository sessionRepository) { + this.serverProperties = serverProperties; + this.sessionRepository = sessionRepository; + } + + @PostConstruct + public void applyConfigurationProperties() { + Integer timeout = this.serverProperties.getSession().getTimeout(); + if (timeout != null) { + this.sessionRepository.setMaxInactiveIntervalInSeconds(timeout); + } + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java index 402f2ed05e..43ae37ebd4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java @@ -18,34 +18,31 @@ package org.springframework.boot.autoconfigure.session; import javax.annotation.PostConstruct; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.session.SessionRepository; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.session.data.redis.RedisOperationsSessionRepository; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; -import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration; /** * Redis backed session auto-configuration. * * @author Andy Wilkinson * @author Tommy Ludwig + * @author Eddú Meléndez * @since 1.4.0 */ @Configuration -@ConditionalOnClass(RedisConnectionFactory.class) -@ConditionalOnMissingBean({ SessionRepository.class, RedisHttpSessionConfiguration.class }) +@ConditionalOnBean({RedisTemplate.class}) @EnableRedisHttpSession @Conditional(SessionCondition.class) class RedisSessionConfiguration { - private final ServerProperties serverProperties; + private ServerProperties serverProperties; - private final RedisOperationsSessionRepository sessionRepository; + private RedisOperationsSessionRepository sessionRepository; RedisSessionConfiguration(ServerProperties serverProperties, RedisOperationsSessionRepository sessionRepository) { this.serverProperties = serverProperties; diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java index a3cb962a98..34635ededb 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java @@ -24,8 +24,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.SessionConfigurationImportSelector; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -39,13 +42,16 @@ import org.springframework.session.SessionRepository; * * @author Andy Wilkinson * @author Tommy Ludwig + * @author Eddú Meléndez * @since 1.3.0 */ @Configuration @ConditionalOnClass(Session.class) @ConditionalOnWebApplication @ConditionalOnMissingBean(SessionRepository.class) -@AutoConfigureAfter({ HazelcastAutoConfiguration.class, RedisAutoConfiguration.class }) +@EnableConfigurationProperties(SessionProperties.class) +@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HazelcastAutoConfiguration.class, + MongoAutoConfiguration.class, RedisAutoConfiguration.class }) @Import(SessionConfigurationImportSelector.class) public class SessionAutoConfiguration { @@ -63,13 +69,13 @@ public class SessionAutoConfiguration { } /** - * {@link ImportSelector} to add {@link SessionStoreType} configuration classes. + * {@link ImportSelector} to add {@link StoreType} configuration classes. */ static class SessionConfigurationImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { - SessionStoreType[] types = SessionStoreType.values(); + StoreType[] types = StoreType.values(); String[] imports = new String[types.length]; for (int i = 0; i < types.length; i++) { imports[i] = SessionStoreMappings.getConfigurationClass(types[i]); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java index a3a3261a8d..5eee81e6a1 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java @@ -38,7 +38,7 @@ class SessionCondition extends SpringBootCondition { if (!resolver.containsProperty("type")) { return ConditionOutcome.match("Automatic session store type"); } - SessionStoreType sessionStoreType = SessionStoreMappings + StoreType sessionStoreType = SessionStoreMappings .getType(((AnnotationMetadata) metadata).getClassName()); String value = resolver.getProperty("type").replace("-", "_").toUpperCase(); if (value.equals(sessionStoreType.name())) { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java index 66b5eea50d..ed2234195e 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java @@ -44,13 +44,13 @@ public class SessionProperties { /** * Session data store type, auto-detected according to the environment by default. */ - private SessionStoreType type; + private StoreType type; - public SessionStoreType getType() { + public StoreType getType() { return this.type; } - public void setType(SessionStoreType type) { + public void setType(StoreType type) { this.type = type; } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java index 50078464af..f296e324d1 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java @@ -23,35 +23,39 @@ import java.util.Map; import org.springframework.util.Assert; /** - * Mappings between {@link SessionStoreType} and {@code @Configuration}. + * Mappings between {@link StoreType} and {@code @Configuration}. * * @author Tommy Ludwig + * @author Eddú Meléndez + * @since 1.4.0 */ final class SessionStoreMappings { + private static final Map> MAPPINGS; + + static { + Map> mappings = new HashMap>(); + mappings.put(StoreType.JDBC, JdbcSessionConfiguration.class); + mappings.put(StoreType.MONGO, MongoSessionConfiguration.class); + mappings.put(StoreType.REDIS, RedisSessionConfiguration.class); + mappings.put(StoreType.HAZELCAST, HazelcastSessionConfiguration.class); + mappings.put(StoreType.HASH_MAP, SimpleSessionConfiguration.class); + mappings.put(StoreType.NONE, NoOpSessionConfiguration.class); + MAPPINGS = Collections.unmodifiableMap(mappings); + } + private SessionStoreMappings() { } - private static final Map> MAPPINGS; - - static { - Map> mappings = new HashMap>(); - mappings.put(SessionStoreType.REDIS, RedisSessionConfiguration.class); - mappings.put(SessionStoreType.HAZELCAST, HazelcastSessionConfiguration.class); - mappings.put(SessionStoreType.SIMPLE, SimpleSessionConfiguration.class); - mappings.put(SessionStoreType.NONE, NoOpSessionConfiguration.class); - MAPPINGS = Collections.unmodifiableMap(mappings); - } - - public static String getConfigurationClass(SessionStoreType sessionStoreType) { + public static String getConfigurationClass(StoreType sessionStoreType) { Class configurationClass = MAPPINGS.get(sessionStoreType); Assert.state(configurationClass != null, "Unknown session store type " + sessionStoreType); return configurationClass.getName(); } - public static SessionStoreType getType(String configurationClassName) { - for (Map.Entry> entry : MAPPINGS.entrySet()) { + public static StoreType getType(String configurationClassName) { + for (Map.Entry> entry : MAPPINGS.entrySet()) { if (entry.getValue().getName().equals(configurationClassName)) { return entry.getKey(); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java index 4334782787..d13ca68db6 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.boot.autoconfigure.session; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -33,7 +32,6 @@ import org.springframework.session.config.annotation.web.http.EnableSpringHttpSe * @since 1.4.0 */ @Configuration -@ConditionalOnMissingBean(SessionRepository.class) @EnableSpringHttpSession @Conditional(SessionCondition.class) class SimpleSessionConfiguration { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreType.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java similarity index 86% rename from spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreType.java rename to spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java index ce508c5896..4a86634029 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreType.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java @@ -20,9 +20,20 @@ package org.springframework.boot.autoconfigure.session; * Supported Spring Session data store types. * * @author Tommy Ludwig + * @author Eddú Meléndez * @since 1.4.0 */ -public enum SessionStoreType { +public enum StoreType { + + /** + * JDBC backed sessions. + */ + JDBC, + + /** + * Mongo backed sessions. + */ + MONGO, /** * Redis backed sessions. @@ -37,7 +48,7 @@ public enum SessionStoreType { /** * Simple in-memory map of sessions. */ - SIMPLE, + HASH_MAP, /** * No session datastore. diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/package-info.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/package-info.java new file mode 100644 index 0000000000..8534bf744d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2016 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. + */ + +/** + * Auto-configuration for Spring Session. + */ +package org.springframework.boot.autoconfigure.session; diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java new file mode 100644 index 0000000000..3f4fb87e66 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2016 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 java.net.UnknownHostException; + +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; + +import com.mongodb.MongoClient; +import org.junit.After; +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.data.mongo.MongoOperationsSessionRepository; +import org.springframework.session.jdbc.JdbcOperationsSessionRepository; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +public class StoreTypesConfigurationTests { + + private AnnotationConfigWebApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void hashMapSessionStore() { + load("spring.session.store.type=hash-map"); + MapSessionRepository sessionRepository = this.context.getBean(MapSessionRepository.class); + assertThat(sessionRepository).isNotNull(); + } + + @Test + public void jdbcSessionStore() { + load(new String[] {"spring.session.store.type=jdbc"}, EmbeddedDataSourceConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class); + assertThat(this.context.getBean(JdbcOperationsSessionRepository.class)).isNotNull(); + } + + @Test + public void hazelcastSessionStore() { + load(new String[] {"spring.session.store.type=hazelcast"}, MockHazelcastInstanceConfiguration.class); + assertThat(this.context.getBean(MapSessionRepository.class)).isNotNull(); + } + + @Test + public void mongoSessionStore() { + load(new String[] {"spring.session.store.type=mongo", "spring.data.mongodb.port=0"}, MockMongoConfiguration.class, MongoDataAutoConfiguration.class, EmbeddedMongoAutoConfiguration.class); + assertThat(this.context.getBean(MongoOperationsSessionRepository.class)).isNotNull(); + } + + private void load(String storeType) { + load(new String[] {storeType}, null); + } + + private void load(String[] storeType, Class... config) { + this.context = new AnnotationConfigWebApplicationContext(); + for (String property : storeType) { + EnvironmentTestUtils.addEnvironment(this.context, storeType); + } + if (config != null) { + this.context.register(config); + } + this.context.register(ServerPropertiesAutoConfiguration.class, + SessionAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + } + + @Configuration + static class MockHazelcastInstanceConfiguration { + + @Bean + public HazelcastInstance hazelcastInstance() { + return Hazelcast.newHazelcastInstance(); + } + + } + + @Configuration + static class MockMongoConfiguration { + + @Bean + public MongoClient mongoClient(@Value("${local.mongo.port}") int port) + throws UnknownHostException { + return new MongoClient("localhost", port); + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java index cbc9f0c9a7..8763fa4e70 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java @@ -34,6 +34,7 @@ import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFac import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory.RegisteredFilter; import org.springframework.boot.web.filter.OrderedCharacterEncodingFilter; import org.springframework.boot.web.filter.OrderedRequestContextFilter; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnection; @@ -49,6 +50,7 @@ import static org.mockito.Mockito.mock; * Integration tests that verify the ordering of various filters that are auto-configured. * * @author Andy Wilkinson + * @author Eddú Meléndez */ public class FilterOrderingIntegrationTests { @@ -82,6 +84,7 @@ public class FilterOrderingIntegrationTests { private void load() { this.context = new AnnotationConfigEmbeddedWebApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "spring.session.store.type=hash-map"); this.context.register(MockEmbeddedServletContainerConfiguration.class, TestRedisConfiguration.class, WebMvcAutoConfiguration.class, SecurityAutoConfiguration.class, SessionAutoConfiguration.class, diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 11888e8804..60a8687f2b 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -356,6 +356,9 @@ content into your application; rather pick only the properties that you need. spring.resources.chain.strategy.fixed.version= # Version string to use for the Version Strategy. spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ # Locations of static resources. + # SPRING SESSION ({sc-spring-boot-autoconfigure}/session/SocialWebAutoConfiguration.{sc-ext}[SessionAutoConfiguration]) + spring.session.store.store-type= # Session store type + # SPRING SOCIAL ({sc-spring-boot-autoconfigure}/social/SocialWebAutoConfiguration.{sc-ext}[SocialWebAutoConfiguration]) spring.social.auto-connection-views=false # Enable the connection status view for supported providers. diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index ea500f111a..ebda36d136 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -4393,11 +4393,21 @@ class for more details. [[boot-features-session]] == Spring Session Spring Session provides support for managing a user's session information. If you are -writing a web application and Spring Session and Spring Data Redis are both on the -classpath, Spring Boot will auto-configure Spring Session through its -`@EnableRedisHttpSession`. Session data will be stored in Redis and the session timeout +writing a web application and Spring Session and one the following providers: + +* JDBC +* Hazelcast +* Spring Data Mongo +* Spring Data Redis + +on the classpath, Spring Boot will auto-configure Spring Session through its +`@EnableJdbcHttpSession`, `@EnableHazelcastHttpSession`, `@EnableMongoHttpSession`, +`@EnableRedisHttpSession`. Session data will be stored in the provider and the session timeout can be configured using the `server.session.timeout` property. +It is also possible to _force_ the session provider to use via the `spring.session.store.type` +property. + [[boot-features-jmx]] From bf89c8ee464f588bf6274105e480aca87ff1aaef Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 18 Apr 2016 13:35:39 +0200 Subject: [PATCH 3/3] Polish spring session auto-configuration This commit improves the initial submission by adding more tests and more configuration options. Closes gh-5158 --- ....java => HashMapSessionConfiguration.java} | 15 +- .../HazelcastSessionConfiguration.java | 38 ++-- .../session/JdbcSessionConfiguration.java | 36 ++-- .../session/MongoSessionConfiguration.java | 36 ++-- .../session/NoOpSessionConfiguration.java | 4 +- .../session/RedisSessionConfiguration.java | 41 ++-- .../session/SessionAutoConfiguration.java | 19 +- .../session/SessionCondition.java | 8 +- .../session/SessionProperties.java | 151 ++++++++++++-- .../session/SessionStoreMappings.java | 4 +- .../boot/autoconfigure/session/StoreType.java | 2 +- ...AbstractSessionAutoConfigurationTests.java | 76 +++++++ .../SessionAutoConfigurationRedisTests.java | 69 +++++++ .../SessionAutoConfigurationTests.java | 194 +++++++++++++----- .../session/StoreTypesConfigurationTests.java | 120 ----------- .../web/FilterOrderingIntegrationTests.java | 5 +- spring-boot-dependencies/pom.xml | 2 +- .../appendix-application-properties.adoc | 9 +- .../main/asciidoc/spring-boot-features.adoc | 23 ++- 19 files changed, 528 insertions(+), 324 deletions(-) rename spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/{SimpleSessionConfiguration.java => HashMapSessionConfiguration.java} (77%) create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java delete mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HashMapSessionConfiguration.java similarity index 77% rename from spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java rename to spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HashMapSessionConfiguration.java index d13ca68db6..182992d083 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SimpleSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HashMapSessionConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.boot.autoconfigure.session; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -26,24 +25,24 @@ import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; /** - * In-memory session configuration, intended as a fallback. + * HashMap based session configuration, intended as a fallback. * * @author Tommy Ludwig - * @since 1.4.0 + * @author Stephane Nicoll */ @Configuration @EnableSpringHttpSession @Conditional(SessionCondition.class) -class SimpleSessionConfiguration { +class HashMapSessionConfiguration { @Bean - public SessionRepository sessionRepository(ServerProperties serverProperties) { + public SessionRepository sessionRepository(SessionProperties sessionProperties) { MapSessionRepository sessionRepository = new MapSessionRepository(); - - Integer timeout = serverProperties.getSession().getTimeout(); - if (serverProperties.getSession().getTimeout() != null) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { sessionRepository.setDefaultMaxInactiveInterval(timeout); } return sessionRepository; } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java index 95f2f15587..6667e2b923 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java @@ -16,44 +16,38 @@ package org.springframework.boot.autoconfigure.session; -import javax.annotation.PostConstruct; - import com.hazelcast.core.HazelcastInstance; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.session.MapSessionRepository; -import org.springframework.session.hazelcast.config.annotation.web.http.EnableHazelcastHttpSession; +import org.springframework.session.hazelcast.config.annotation.web.http.HazelcastHttpSessionConfiguration; /** - * Hazelcast backed session auto-configuration. + * Hazelcast backed session configuration. * * @author Tommy Ludwig * @author Eddú Meléndez - * @since 1.4.0 + * @author Stephane Nicoll */ @Configuration -@ConditionalOnBean({ HazelcastInstance.class }) -@EnableHazelcastHttpSession +@ConditionalOnBean(HazelcastInstance.class) @Conditional(SessionCondition.class) class HazelcastSessionConfiguration { - private ServerProperties serverProperties; + @Configuration + public static class SprigBootHazelcastHttpSessionConfiguration + extends HazelcastHttpSessionConfiguration { - private MapSessionRepository sessionRepository; - - HazelcastSessionConfiguration(ServerProperties serverProperties, MapSessionRepository sessionRepository) { - this.serverProperties = serverProperties; - this.sessionRepository = sessionRepository; - } - - @PostConstruct - public void applyConfigurationProperties() { - Integer timeout = this.serverProperties.getSession().getTimeout(); - if (timeout != null) { - this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + @Autowired + public void customize(SessionProperties sessionProperties) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { + setMaxInactiveIntervalInSeconds(timeout); + } + setSessionMapName(sessionProperties.getHazelcast().getMapName()); } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java index 7a4c8f7fad..ce82048e2c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java @@ -16,44 +16,38 @@ package org.springframework.boot.autoconfigure.session; -import javax.annotation.PostConstruct; import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.session.jdbc.JdbcOperationsSessionRepository; -import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; +import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration; /** - * JDBC backed session auto-configuration. + * JDBC backed session configuration. * * @author Eddú Meléndez - * @since 1.4.0 + * @author Stephane Nicoll */ @Configuration @ConditionalOnBean(DataSource.class) -@EnableJdbcHttpSession @Conditional(SessionCondition.class) class JdbcSessionConfiguration { - private ServerProperties serverProperties; + @Configuration + public static class SpringBootJdbcHttpSessionConfiguration + extends JdbcHttpSessionConfiguration { - private JdbcOperationsSessionRepository sessionRepository; - - JdbcSessionConfiguration(ServerProperties serverProperties, - JdbcOperationsSessionRepository sessionRepository) { - this.serverProperties = serverProperties; - this.sessionRepository = sessionRepository; - } - - @PostConstruct - public void applyConfigurationProperties() { - Integer timeout = this.serverProperties.getSession().getTimeout(); - if (timeout != null) { - this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + @Autowired + public void customize(SessionProperties sessionProperties) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { + setMaxInactiveIntervalInSeconds(timeout); + } + setTableName(sessionProperties.getJdbc().getTableName()); } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java index 1eb6f9d86c..2c8ac38703 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java @@ -16,43 +16,37 @@ package org.springframework.boot.autoconfigure.session; -import javax.annotation.PostConstruct; - +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.session.data.mongo.MongoOperationsSessionRepository; -import org.springframework.session.data.mongo.config.annotation.web.http.EnableMongoHttpSession; +import org.springframework.session.data.mongo.config.annotation.web.http.MongoHttpSessionConfiguration; /** - * Mongo backed session auto-configuration. + * Mongo backed session configuration. * * @author Eddú Meléndez - * @since 1.4.0 + * @author Stephane Nicoll */ @Configuration @ConditionalOnBean(MongoOperations.class) -@EnableMongoHttpSession @Conditional(SessionCondition.class) class MongoSessionConfiguration { - private ServerProperties serverProperties; + @Configuration + public static class SpringBootMongoHttpSessionConfiguration + extends MongoHttpSessionConfiguration { - private MongoOperationsSessionRepository sessionRepository; - - MongoSessionConfiguration(ServerProperties serverProperties, MongoOperationsSessionRepository sessionRepository) { - this.serverProperties = serverProperties; - this.sessionRepository = sessionRepository; - } - - @PostConstruct - public void applyConfigurationProperties() { - Integer timeout = this.serverProperties.getSession().getTimeout(); - if (timeout != null) { - this.sessionRepository.setMaxInactiveIntervalInSeconds(timeout); + @Autowired + public void customize(SessionProperties sessionProperties) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { + setMaxInactiveIntervalInSeconds(timeout); + } + setCollectionName(sessionProperties.getMongo().getCollectionName()); } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java index 581ac958c0..c374c1bd2f 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/NoOpSessionConfiguration.java @@ -22,11 +22,9 @@ import org.springframework.context.annotation.Configuration; import org.springframework.session.SessionRepository; /** - * No-op session configuration used to disable Spring Session auto configuration via the - * environment. + * No-op session configuration used to disable Spring Session using the environment. * * @author Tommy Ludwig - * @since 1.4.0 */ @Configuration @ConditionalOnMissingBean(SessionRepository.class) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java index 43ae37ebd4..34413198b5 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java @@ -16,45 +16,42 @@ package org.springframework.boot.autoconfigure.session; -import javax.annotation.PostConstruct; - +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.session.data.redis.RedisOperationsSessionRepository; -import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; +import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration; /** - * Redis backed session auto-configuration. + * Redis backed session configuration. * * @author Andy Wilkinson * @author Tommy Ludwig * @author Eddú Meléndez - * @since 1.4.0 + * @author Stephane Nicoll */ @Configuration -@ConditionalOnBean({RedisTemplate.class}) -@EnableRedisHttpSession +@ConditionalOnBean({ RedisTemplate.class, RedisConnectionFactory.class }) @Conditional(SessionCondition.class) class RedisSessionConfiguration { - private ServerProperties serverProperties; + @Configuration + public static class SpringBootRedisHttpSessionConfiguration + extends RedisHttpSessionConfiguration { - private RedisOperationsSessionRepository sessionRepository; - - RedisSessionConfiguration(ServerProperties serverProperties, RedisOperationsSessionRepository sessionRepository) { - this.serverProperties = serverProperties; - this.sessionRepository = sessionRepository; - } - - @PostConstruct - public void applyConfigurationProperties() { - Integer timeout = this.serverProperties.getSession().getTimeout(); - if (timeout != null) { - this.sessionRepository.setDefaultMaxInactiveInterval(timeout); + @Autowired + public void customize(SessionProperties sessionProperties) { + Integer timeout = sessionProperties.getTimeout(); + if (timeout != null) { + setMaxInactiveIntervalInSeconds(timeout); + } + SessionProperties.Redis redis = sessionProperties.getRedis(); + setRedisNamespace(redis.getNamespace()); + setRedisFlushMode(redis.getFlushMode()); } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java index 34635ededb..27f45adcd3 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java @@ -21,15 +21,12 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.SessionConfigurationImportSelector; -import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportSelector; @@ -43,7 +40,8 @@ import org.springframework.session.SessionRepository; * @author Andy Wilkinson * @author Tommy Ludwig * @author Eddú Meléndez - * @since 1.3.0 + * @author Stephane Nicoll + * @since 1.4.0 */ @Configuration @ConditionalOnClass(Session.class) @@ -55,19 +53,6 @@ import org.springframework.session.SessionRepository; @Import(SessionConfigurationImportSelector.class) public class SessionAutoConfiguration { - @Configuration - @ConditionalOnMissingBean(value = ServerProperties.class, search = SearchStrategy.CURRENT) - // Just in case user switches off ServerPropertiesAutoConfiguration - public static class ServerPropertiesConfiguration { - - @Bean - // Use the same bean name as the default one for any old webapp - public ServerProperties serverProperties() { - return new ServerProperties(); - } - - } - /** * {@link ImportSelector} to add {@link StoreType} configuration classes. */ diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java index 5eee81e6a1..bd9bbbeb98 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionCondition.java @@ -24,7 +24,7 @@ import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotationMetadata; /** - * General condition used by all session auto-configuration classes. + * General condition used with all session configuration classes. * * @author Tommy Ludwig */ @@ -34,13 +34,13 @@ class SessionCondition extends SpringBootCondition { public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( - context.getEnvironment(), "spring.session.store."); - if (!resolver.containsProperty("type")) { + context.getEnvironment(), "spring.session."); + if (!resolver.containsProperty("store-type")) { return ConditionOutcome.match("Automatic session store type"); } StoreType sessionStoreType = SessionStoreMappings .getType(((AnnotationMetadata) metadata).getClassName()); - String value = resolver.getProperty("type").replace("-", "_").toUpperCase(); + String value = resolver.getProperty("store-type").replace("-", "_").toUpperCase(); if (value.equals(sessionStoreType.name())) { return ConditionOutcome.match("Session store type " + sessionStoreType); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java index ed2234195e..33e111c25e 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java @@ -16,42 +16,153 @@ package org.springframework.boot.autoconfigure.session; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.session.data.redis.RedisFlushMode; /** - * Properties for configuring Spring Session's auto-configuration. + * Configuration properties for Spring Session. * * @author Tommy Ludwig + * @author Stephane Nicoll * @since 1.4.0 */ @ConfigurationProperties("spring.session") public class SessionProperties { - private Store store; + /** + * Session store type, auto-detected according to the environment by default. + */ + private StoreType storeType; - public Store getStore() { - return this.store; + private Integer timeout; + + private final Hazelcast hazelcast = new Hazelcast(); + + private final Jdbc jdbc = new Jdbc(); + + private final Mongo mongo = new Mongo(); + + private final Redis redis = new Redis(); + + public SessionProperties(ObjectProvider serverProperties) { + ServerProperties properties = serverProperties.getIfUnique(); + this.timeout = (properties != null ? properties.getSession().getTimeout() : null); } - public void setStore(Store store) { - this.store = store; + public StoreType getStoreType() { + return this.storeType; + } + + public void setStoreType(StoreType storeType) { + this.storeType = storeType; } /** - * Session store-specific properties. + * Return the session timeout in seconds. + * @return the session timeout in seconds + * @see ServerProperties#getSession() */ - public static class Store { - /** - * Session data store type, auto-detected according to the environment by default. - */ - private StoreType type; - - public StoreType getType() { - return this.type; - } - - public void setType(StoreType type) { - this.type = type; - } + public Integer getTimeout() { + return this.timeout; } + + public Hazelcast getHazelcast() { + return this.hazelcast; + } + + public Jdbc getJdbc() { + return this.jdbc; + } + + public Mongo getMongo() { + return this.mongo; + } + + public Redis getRedis() { + return this.redis; + } + + public static class Hazelcast { + + /** + * Name of the map used to store sessions. + */ + private String mapName = "spring:session:sessions"; + + public String getMapName() { + return this.mapName; + } + + public void setMapName(String mapName) { + this.mapName = mapName; + } + + } + + public static class Jdbc { + + /** + * Name of database table used to store sessions. + */ + private String tableName = "SPRING_SESSION"; + + public String getTableName() { + return this.tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + } + + public static class Mongo { + + /** + * Collection name used to store sessions. + */ + private String collectionName = "sessions"; + + public String getCollectionName() { + return this.collectionName; + } + + public void setCollectionName(String collectionName) { + this.collectionName = collectionName; + } + + } + + public static class Redis { + + /** + * Namespace for keys used to store sessions. + */ + private String namespace = ""; + + /** + * Flush mode for the Redis sessions. + */ + private RedisFlushMode flushMode = RedisFlushMode.ON_SAVE; + + public String getNamespace() { + return this.namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public RedisFlushMode getFlushMode() { + return this.flushMode; + } + + public void setFlushMode(RedisFlushMode flushMode) { + this.flushMode = flushMode; + } + + } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java index f296e324d1..81ce310bbf 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/SessionStoreMappings.java @@ -27,7 +27,6 @@ import org.springframework.util.Assert; * * @author Tommy Ludwig * @author Eddú Meléndez - * @since 1.4.0 */ final class SessionStoreMappings { @@ -39,7 +38,7 @@ final class SessionStoreMappings { mappings.put(StoreType.MONGO, MongoSessionConfiguration.class); mappings.put(StoreType.REDIS, RedisSessionConfiguration.class); mappings.put(StoreType.HAZELCAST, HazelcastSessionConfiguration.class); - mappings.put(StoreType.HASH_MAP, SimpleSessionConfiguration.class); + mappings.put(StoreType.HASH_MAP, HashMapSessionConfiguration.class); mappings.put(StoreType.NONE, NoOpSessionConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); } @@ -63,4 +62,5 @@ final class SessionStoreMappings { throw new IllegalStateException( "Unknown configuration class " + configurationClassName); } + } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java index 4a86634029..0be5627260 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/StoreType.java @@ -51,7 +51,7 @@ public enum StoreType { HASH_MAP, /** - * No session datastore. + * No session data-store. */ NONE; diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java new file mode 100644 index 0000000000..0c7f462d4c --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2016 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 java.util.Collection; + +import org.junit.After; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.session.SessionRepository; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Share test utilities for {@link SessionAutoConfiguration} tests. + * + * @author Stephane Nicoll + */ +public abstract class AbstractSessionAutoConfigurationTests { + + protected AnnotationConfigWebApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + protected > T validateSessionRepository(Class type) { + SessionRepository cacheManager = this.context.getBean(SessionRepository.class); + assertThat(cacheManager).as("Wrong session repository type").isInstanceOf(type); + return type.cast(cacheManager); + } + + protected Integer getSessionTimeout(SessionRepository sessionRepository) { + return (Integer) new DirectFieldAccessor(sessionRepository) + .getPropertyValue("defaultMaxInactiveInterval"); + } + + protected void load(String... environment) { + load(null, environment); + } + + protected void load(Collection> configs, String... environment) { + AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); + EnvironmentTestUtils.addEnvironment(ctx, environment); + if (configs != null) { + ctx.register(configs.toArray(new Class[configs.size()])); + } + ctx.register(ServerPropertiesAutoConfiguration.class, + SessionAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + ctx.refresh(); + this.context = ctx; + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java new file mode 100644 index 0000000000..2647063842 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2016 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 java.util.Collections; + +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.redis.RedisTestServer; +import org.springframework.session.data.redis.RedisFlushMode; +import org.springframework.session.data.redis.RedisOperationsSessionRepository; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Redis specific tests for {@link SessionAutoConfiguration}. + * + * @author Stephane Nicoll + */ +public class SessionAutoConfigurationRedisTests + extends AbstractSessionAutoConfigurationTests { + + @Rule + public final RedisTestServer redis = new RedisTestServer(); + + @Test + public void redisSessionStore() { + load(Collections.>singletonList(RedisAutoConfiguration.class), + "spring.session.store-type=redis"); + RedisOperationsSessionRepository repository = validateSessionRepository( + RedisOperationsSessionRepository.class); + assertThat(repository.getSessionCreatedChannelPrefix()) + .isEqualTo("spring:session:event:created:"); + assertThat(new DirectFieldAccessor(repository).getPropertyValue("redisFlushMode")) + .isEqualTo(RedisFlushMode.ON_SAVE); + } + + @Test + public void redisSessionStoreWithCustomizations() { + load(Collections.>singletonList(RedisAutoConfiguration.class), + "spring.session.store-type=redis", + "spring.session.redis.namespace=foo", + "spring.session.redis.flush-mode=immediate"); + RedisOperationsSessionRepository repository = validateSessionRepository( + RedisOperationsSessionRepository.class); + assertThat(repository.getSessionCreatedChannelPrefix()) + .isEqualTo("spring:session:foo:event:created:"); + assertThat(new DirectFieldAccessor(repository).getPropertyValue("redisFlushMode")) + .isEqualTo(RedisFlushMode.IMMEDIATE); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java index e07f8327d4..1620e71b59 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java @@ -16,76 +16,174 @@ package org.springframework.boot.autoconfigure.session; -import org.junit.After; -import org.junit.Rule; +import java.util.Arrays; +import java.util.Collections; + +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.core.IMap; import org.junit.Test; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; -import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; -import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; -import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory; -import org.springframework.boot.redis.RedisTestServer; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.session.ExpiringSession; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.SessionRepository; +import org.springframework.session.data.mongo.MongoOperationsSessionRepository; +import org.springframework.session.jdbc.JdbcOperationsSessionRepository; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; /** * Tests for {@link SessionAutoConfiguration}. * * @author Dave Syer - * @since 1.3.0 + * @author Eddú Meléndez + * @author Stephane Nicoll */ -public class SessionAutoConfigurationTests { +public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests { - @Rule - public RedisTestServer redis = new RedisTestServer(); + @Test + public void backOffIfSessionRepositoryIsPresent() { + load(Collections.>singletonList(SessionRepositoryConfiguration.class), + "spring.session.store-type=mongo"); + MapSessionRepository repository = validateSessionRepository( + MapSessionRepository.class); + assertThat(this.context.getBean("mySessionRepository")).isSameAs(repository); + } - private AnnotationConfigEmbeddedWebApplicationContext context; + @Test + public void hashMapSessionStore() { + load("spring.session.store-type=hash-map"); + MapSessionRepository repository = validateSessionRepository( + MapSessionRepository.class); + assertThat(getSessionTimeout(repository)).isNull(); + } - @After - public void close() { - if (this.context != null) { - this.context.close(); + @Test + public void hashMapSessionStoreCustomTimeout() { + load("spring.session.store-type=hash-map", + "server.session.timeout=3000"); + MapSessionRepository repository = validateSessionRepository( + MapSessionRepository.class); + assertThat(getSessionTimeout(repository)).isEqualTo(3000); + } + + @Test + public void springSessionTimeoutIsNotAValidProperty() { + load("spring.session.store-type=hash-map", + "spring.session.timeout=3000"); + MapSessionRepository repository = validateSessionRepository( + MapSessionRepository.class); + assertThat(getSessionTimeout(repository)).isNull(); + } + + @Test + public void hashMapSessionStoreIsDefault() { + load(); + validateSessionRepository(MapSessionRepository.class); + } + + @Test + public void jdbcSessionStore() { + load(Arrays.asList(EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class), + "spring.session.store-type=jdbc"); + JdbcOperationsSessionRepository repository = validateSessionRepository( + JdbcOperationsSessionRepository.class); + assertThat(new DirectFieldAccessor(repository).getPropertyValue("tableName")) + .isEqualTo("SPRING_SESSION"); + } + + @Test + public void jdbcSessionStoreCustomTableName() { + load(Arrays.asList(EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class), + "spring.session.store-type=jdbc", + "spring.session.jdbc.table-name=FOO_BAR"); + JdbcOperationsSessionRepository repository = validateSessionRepository( + JdbcOperationsSessionRepository.class); + assertThat(new DirectFieldAccessor(repository).getPropertyValue("tableName")) + .isEqualTo("FOO_BAR"); + } + + @Test + public void hazelcastSessionStore() { + load(Collections.>singletonList(HazelcastConfiguration.class), + "spring.session.store-type=hazelcast"); + validateSessionRepository(MapSessionRepository.class); + } + + @Test + public void hazelcastSessionStoreWithCustomizations() { + load(Collections.>singletonList(HazelcastSpecificMap.class), + "spring.session.store-type=hazelcast", + "spring.session.hazelcast.map-name=foo:bar:biz"); + validateSessionRepository(MapSessionRepository.class); + HazelcastInstance hazelcastInstance = this.context.getBean(HazelcastInstance.class); + verify(hazelcastInstance, times(1)).getMap("foo:bar:biz"); + } + + @Test + public void mongoSessionStore() { + load(Arrays.asList(EmbeddedMongoAutoConfiguration.class, + MongoAutoConfiguration.class, MongoDataAutoConfiguration.class), + "spring.session.store-type=mongo", "spring.data.mongodb.port=0"); + validateSessionRepository(MongoOperationsSessionRepository.class); + } + + @Test + public void mongoSessionStoreWithCustomizations() { + load(Arrays.asList(EmbeddedMongoAutoConfiguration.class, + MongoAutoConfiguration.class, MongoDataAutoConfiguration.class), + "spring.session.store-type=mongo", "spring.data.mongodb.port=0", + "spring.session.mongo.collection-name=foobar"); + MongoOperationsSessionRepository repository = validateSessionRepository( + MongoOperationsSessionRepository.class); + assertThat(new DirectFieldAccessor(repository).getPropertyValue("collectionName")) + .isEqualTo("foobar"); + } + + + @Configuration + static class SessionRepositoryConfiguration { + + @Bean + public SessionRepository mySessionRepository() { + return new MapSessionRepository(Collections.emptyMap()); } - } - @Test - public void flat() throws Exception { - this.context = new AnnotationConfigEmbeddedWebApplicationContext(); - this.context.register(Config.class, ServerPropertiesAutoConfiguration.class, - RedisAutoConfiguration.class, SessionAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - ServerProperties server = this.context.getBean(ServerProperties.class); - assertThat(server).isNotNull(); - } - - @Test - public void hierarchy() throws Exception { - AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext(); - parent.register(RedisAutoConfiguration.class, SessionAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - parent.refresh(); - this.context = new AnnotationConfigEmbeddedWebApplicationContext(); - this.context.setParent(parent); - this.context.register(Config.class, ServerPropertiesAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - ServerProperties server = this.context.getBean(ServerProperties.class); - assertThat(server).isNotNull(); } @Configuration - protected static class Config { + static class HazelcastConfiguration { @Bean - public EmbeddedServletContainerFactory containerFactory() { - return new MockEmbeddedServletContainerFactory(); + public HazelcastInstance hazelcastInstance() { + return Hazelcast.newHazelcastInstance(); + } + + } + + @Configuration + static class HazelcastSpecificMap { + + @Bean + public HazelcastInstance hazelcastInstance() { + IMap map = mock(IMap.class); + HazelcastInstance mock = mock(HazelcastInstance.class); + given(mock.getMap("foo:bar:biz")).willReturn(map); + return mock; } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java deleted file mode 100644 index 3f4fb87e66..0000000000 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/StoreTypesConfigurationTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2012-2016 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 java.net.UnknownHostException; - -import com.hazelcast.core.Hazelcast; -import com.hazelcast.core.HazelcastInstance; - -import com.mongodb.MongoClient; -import org.junit.After; -import org.junit.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; -import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; -import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; -import org.springframework.boot.test.util.EnvironmentTestUtils; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.session.MapSessionRepository; -import org.springframework.session.data.mongo.MongoOperationsSessionRepository; -import org.springframework.session.jdbc.JdbcOperationsSessionRepository; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; - -public class StoreTypesConfigurationTests { - - private AnnotationConfigWebApplicationContext context; - - @After - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void hashMapSessionStore() { - load("spring.session.store.type=hash-map"); - MapSessionRepository sessionRepository = this.context.getBean(MapSessionRepository.class); - assertThat(sessionRepository).isNotNull(); - } - - @Test - public void jdbcSessionStore() { - load(new String[] {"spring.session.store.type=jdbc"}, EmbeddedDataSourceConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class); - assertThat(this.context.getBean(JdbcOperationsSessionRepository.class)).isNotNull(); - } - - @Test - public void hazelcastSessionStore() { - load(new String[] {"spring.session.store.type=hazelcast"}, MockHazelcastInstanceConfiguration.class); - assertThat(this.context.getBean(MapSessionRepository.class)).isNotNull(); - } - - @Test - public void mongoSessionStore() { - load(new String[] {"spring.session.store.type=mongo", "spring.data.mongodb.port=0"}, MockMongoConfiguration.class, MongoDataAutoConfiguration.class, EmbeddedMongoAutoConfiguration.class); - assertThat(this.context.getBean(MongoOperationsSessionRepository.class)).isNotNull(); - } - - private void load(String storeType) { - load(new String[] {storeType}, null); - } - - private void load(String[] storeType, Class... config) { - this.context = new AnnotationConfigWebApplicationContext(); - for (String property : storeType) { - EnvironmentTestUtils.addEnvironment(this.context, storeType); - } - if (config != null) { - this.context.register(config); - } - this.context.register(ServerPropertiesAutoConfiguration.class, - SessionAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - } - - @Configuration - static class MockHazelcastInstanceConfiguration { - - @Bean - public HazelcastInstance hazelcastInstance() { - return Hazelcast.newHazelcastInstance(); - } - - } - - @Configuration - static class MockMongoConfiguration { - - @Bean - public MongoClient mongoClient(@Value("${local.mongo.port}") int port) - throws UnknownHostException { - return new MongoClient("localhost", port); - } - - } - -} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java index 8763fa4e70..7acc414a10 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/FilterOrderingIntegrationTests.java @@ -32,9 +32,9 @@ import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebAppl import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor; import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.MockEmbeddedServletContainerFactory.RegisteredFilter; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.web.filter.OrderedCharacterEncodingFilter; import org.springframework.boot.web.filter.OrderedRequestContextFilter; -import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnection; @@ -84,9 +84,10 @@ public class FilterOrderingIntegrationTests { private void load() { this.context = new AnnotationConfigEmbeddedWebApplicationContext(); - EnvironmentTestUtils.addEnvironment(this.context, "spring.session.store.type=hash-map"); + EnvironmentTestUtils.addEnvironment(this.context, "spring.session.store-type=hash-map"); this.context.register(MockEmbeddedServletContainerConfiguration.class, TestRedisConfiguration.class, WebMvcAutoConfiguration.class, + ServerPropertiesAutoConfiguration.class, SecurityAutoConfiguration.class, SessionAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 2c25fc874b..a39573d989 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -157,7 +157,7 @@ 4.0.4.RELEASE 1.0.4.RELEASE 2.0.9.RELEASE - 1.2.0.RC2 + 1.2.0.RC3 1.1.4.RELEASE 2.0.3.RELEASE 1.0.2.RELEASE diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 60a8687f2b..67401c8dcb 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -356,8 +356,13 @@ content into your application; rather pick only the properties that you need. spring.resources.chain.strategy.fixed.version= # Version string to use for the Version Strategy. spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ # Locations of static resources. - # SPRING SESSION ({sc-spring-boot-autoconfigure}/session/SocialWebAutoConfiguration.{sc-ext}[SessionAutoConfiguration]) - spring.session.store.store-type= # Session store type + # SPRING SESSION ({sc-spring-boot-autoconfigure}/session/SessionProperties.{sc-ext}[SessionProperties]) + spring.session.hazelcast.map-name=spring:session:sessions # Name of the map used to store sessions. + spring.session.jdbc.table-name=SPRING_SESSION # Name of database table used to store sessions. + spring.session.mongo.collection-name=sessions # Collection name used to store sessions. + spring.session.redis.flush-mode= # Flush mode for the Redis sessions. + spring.session.redis.namespace= # Namespace for keys used to store sessions. + spring.session.store-type= # Session store type, auto-detected according to the environment by default. # SPRING SOCIAL ({sc-spring-boot-autoconfigure}/social/SocialWebAutoConfiguration.{sc-ext}[SocialWebAutoConfiguration]) spring.social.auto-connection-views=false # Enable the connection status view for supported providers. diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index ebda36d136..9c872e013e 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -4392,21 +4392,24 @@ class for more details. [[boot-features-session]] == Spring Session -Spring Session provides support for managing a user's session information. If you are -writing a web application and Spring Session and one the following providers: +Spring Boot provides Spring Session auto-configuration for a wide range of stores. If +Spring Session is available and you haven't defined a bean of type `SessionRepository`, +Spring Boot tries to detect the following session stores (in this order): * JDBC +* MongoDB +* Redis * Hazelcast -* Spring Data Mongo -* Spring Data Redis +* HashMap -on the classpath, Spring Boot will auto-configure Spring Session through its -`@EnableJdbcHttpSession`, `@EnableHazelcastHttpSession`, `@EnableMongoHttpSession`, -`@EnableRedisHttpSession`. Session data will be stored in the provider and the session timeout -can be configured using the `server.session.timeout` property. +It is also possible to _force_ the store to use via the `spring.session.store-type` +property. Each store have specific additional settings. For instance it is possible +to customize the name of the table for the jdbc store: -It is also possible to _force_ the session provider to use via the `spring.session.store.type` -property. +[source,properties,indent=0] +---- + spring.session.jdbc.table-name=SESSIONS +----