diff --git a/settings.gradle b/settings.gradle index 675c524991..90326e23a1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -107,6 +107,11 @@ include "spring-boot-project:spring-boot-security-oauth2-client" include "spring-boot-project:spring-boot-security-oauth2-resource-server" include "spring-boot-project:spring-boot-security-saml2" include "spring-boot-project:spring-boot-sendgrid" +include "spring-boot-project:spring-boot-session" +include "spring-boot-project:spring-boot-session-data-mongodb" +include "spring-boot-project:spring-boot-session-data-redis" +include "spring-boot-project:spring-boot-session-hazelcast" +include "spring-boot-project:spring-boot-session-jdbc" include "spring-boot-project:spring-boot-test" include "spring-boot-project:spring-boot-test-autoconfigure" include "spring-boot-project:spring-boot-test-integration-tests" diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index 427b85b04e..34b31aca8c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -60,6 +60,7 @@ dependencies { optional(project(":spring-boot-project:spring-boot-security-oauth2-client")) optional(project(":spring-boot-project:spring-boot-security-oauth2-resource-server")) optional(project(":spring-boot-project:spring-boot-security-saml2")) + optional(project(":spring-boot-project:spring-boot-session")) optional(project(":spring-boot-project:spring-boot-tomcat")) optional(project(":spring-boot-project:spring-boot-undertow")) optional(project(":spring-boot-project:spring-boot-validation")) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java index a9aae6385d..8e9c91fbfb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -27,7 +27,6 @@ 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.ConditionalOnWebApplication.Type; -import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.FindByIndexNameSessionRepository; @@ -42,7 +41,7 @@ import org.springframework.session.SessionRepository; * @author Vedran Pavic * @since 2.0.0 */ -@AutoConfiguration(after = SessionAutoConfiguration.class) +@AutoConfiguration(afterName = "org.springframework.boot.session.autoconfigure.SessionAutoConfiguration") @ConditionalOnClass(Session.class) @ConditionalOnAvailableEndpoint(SessionsEndpoint.class) public class SessionsEndpointAutoConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure-all/build.gradle b/spring-boot-project/spring-boot-autoconfigure-all/build.gradle index 7424c6c2c3..41ea465497 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure-all/build.gradle @@ -3,7 +3,6 @@ plugins { id "org.springframework.boot.auto-configuration" id "org.springframework.boot.configuration-properties" id "org.springframework.boot.deployed" - id "org.springframework.boot.docker-test" id "org.springframework.boot.optional-dependencies" } @@ -23,20 +22,6 @@ dependencies { api(project(":spring-boot-project:spring-boot-all")) api(project(":spring-boot-project:spring-boot-autoconfigure")) - dockerTestImplementation(project(":spring-boot-project:spring-boot-test")) - dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) - dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot"))) - dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot-autoconfigure"))) - dockerTestImplementation("com.redis:testcontainers-redis") - dockerTestImplementation("org.assertj:assertj-core") - dockerTestImplementation("org.awaitility:awaitility") - dockerTestImplementation("org.junit.jupiter:junit-jupiter") - dockerTestImplementation("org.mockito:mockito-core") - dockerTestImplementation("org.springframework:spring-test") - dockerTestImplementation("org.testcontainers:junit-jupiter") - dockerTestImplementation("org.testcontainers:mongodb") - dockerTestImplementation("org.testcontainers:testcontainers") - optional(project(":spring-boot-project:spring-boot-activemq")) optional(project(":spring-boot-project:spring-boot-artemis")) optional(project(":spring-boot-project:spring-boot-data-jpa")) @@ -58,6 +43,7 @@ dependencies { optional(project(":spring-boot-project:spring-boot-rsocket")) optional(project(":spring-boot-project:spring-boot-security")) optional(project(":spring-boot-project:spring-boot-security-oauth2-resource-server")) + optional(project(":spring-boot-project:spring-boot-session")) optional(project(":spring-boot-project:spring-boot-tomcat")) optional(project(":spring-boot-project:spring-boot-tx")) optional(project(":spring-boot-project:spring-boot-validation")) @@ -162,11 +148,6 @@ dependencies { exclude group: "org.opensaml", module: "opensaml-saml-impl" } optional("org.springframework.security:spring-security-web") - optional("org.springframework.session:spring-session-core") - optional("org.springframework.session:spring-session-data-mongodb") - optional("org.springframework.session:spring-session-data-redis") - optional("org.springframework.session:spring-session-hazelcast") - optional("org.springframework.session:spring-session-jdbc") optional("org.springframework.amqp:spring-rabbit") optional("org.springframework.amqp:spring-rabbit-stream") diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/MongoReactiveSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/MongoReactiveSessionConfiguration.java deleted file mode 100644 index 2010c042df..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/MongoReactiveSessionConfiguration.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2012-2023 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 - * - * https://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.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.mongodb.core.ReactiveMongoOperations; -import org.springframework.session.ReactiveSessionRepository; -import org.springframework.session.config.ReactiveSessionRepositoryCustomizer; -import org.springframework.session.data.mongo.ReactiveMongoSessionRepository; -import org.springframework.session.data.mongo.config.annotation.web.reactive.ReactiveMongoWebSessionConfiguration; - -/** - * Mongo-backed reactive session configuration. - * - * @author Andy Wilkinson - * @author Weix Sun - * @author Vedran Pavic - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass({ ReactiveMongoOperations.class, ReactiveMongoSessionRepository.class }) -@ConditionalOnMissingBean(ReactiveSessionRepository.class) -@ConditionalOnBean(ReactiveMongoOperations.class) -@EnableConfigurationProperties(MongoSessionProperties.class) -@Import(ReactiveMongoWebSessionConfiguration.class) -class MongoReactiveSessionConfiguration { - - @Bean - ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( - SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties, - ServerProperties serverProperties) { - return (sessionRepository) -> { - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(sessionProperties.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); - map.from(mongoSessionProperties::getCollectionName).to(sessionRepository::setCollectionName); - }; - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java deleted file mode 100644 index 97910540f9..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionConfiguration.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2012-2023 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 - * - * https://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.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.session.SessionRepository; -import org.springframework.session.config.SessionRepositoryCustomizer; -import org.springframework.session.data.mongo.MongoIndexedSessionRepository; -import org.springframework.session.data.mongo.config.annotation.web.http.MongoHttpSessionConfiguration; - -/** - * Mongo-backed session configuration. - * - * @author Eddú Meléndez - * @author Stephane Nicoll - * @author Vedran Pavic - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass({ MongoOperations.class, MongoIndexedSessionRepository.class }) -@ConditionalOnMissingBean(SessionRepository.class) -@ConditionalOnBean(MongoOperations.class) -@EnableConfigurationProperties(MongoSessionProperties.class) -@Import(MongoHttpSessionConfiguration.class) -class MongoSessionConfiguration { - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - SessionRepositoryCustomizer springBootSessionRepositoryCustomizer( - SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties, - ServerProperties serverProperties) { - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - return (sessionRepository) -> { - map.from(sessionProperties.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); - map.from(mongoSessionProperties::getCollectionName).to(sessionRepository::setCollectionName); - }; - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java deleted file mode 100644 index e2ee0c7f50..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2012-2025 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 - * - * https://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.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; -import org.springframework.session.ReactiveSessionRepository; -import org.springframework.session.config.ReactiveSessionRepositoryCustomizer; -import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository; -import org.springframework.session.data.redis.ReactiveRedisSessionRepository; -import org.springframework.session.data.redis.config.ConfigureReactiveRedisAction; -import org.springframework.session.data.redis.config.annotation.ConfigureNotifyKeyspaceEventsReactiveAction; -import org.springframework.session.data.redis.config.annotation.web.server.RedisIndexedWebSessionConfiguration; -import org.springframework.session.data.redis.config.annotation.web.server.RedisWebSessionConfiguration; - -/** - * Redis-backed reactive session configuration. - * - * @author Andy Wilkinson - * @author Weix Sun - * @author Vedran Pavic - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass({ ReactiveRedisConnectionFactory.class, ReactiveRedisSessionRepository.class }) -@ConditionalOnMissingBean(ReactiveSessionRepository.class) -@ConditionalOnBean(ReactiveRedisConnectionFactory.class) -@EnableConfigurationProperties(RedisSessionProperties.class) -class RedisReactiveSessionConfiguration { - - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "default", - matchIfMissing = true) - @Import(RedisWebSessionConfiguration.class) - static class DefaultRedisSessionConfiguration { - - @Bean - ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( - SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, - ServerProperties serverProperties) { - return (sessionRepository) -> { - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(sessionProperties - .determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); - map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); - map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); - }; - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "indexed") - @Import(RedisIndexedWebSessionConfiguration.class) - static class IndexedRedisSessionConfiguration { - - @Bean - @ConditionalOnMissingBean - ConfigureReactiveRedisAction configureReactiveRedisAction(RedisSessionProperties redisSessionProperties) { - return switch (redisSessionProperties.getConfigureAction()) { - case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsReactiveAction(); - case NONE -> ConfigureReactiveRedisAction.NO_OP; - }; - } - - @Bean - ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( - SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, - ServerProperties serverProperties) { - return (sessionRepository) -> { - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(sessionProperties - .determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); - map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); - map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); - }; - } - - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java b/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java deleted file mode 100644 index 659997010d..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionConfiguration.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2012-2025 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 - * - * https://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.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.session.SessionRepository; -import org.springframework.session.config.SessionRepositoryCustomizer; -import org.springframework.session.data.redis.RedisIndexedSessionRepository; -import org.springframework.session.data.redis.RedisSessionRepository; -import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction; -import org.springframework.session.data.redis.config.ConfigureRedisAction; -import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration; -import org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration; - -/** - * Redis backed session configuration. - * - * @author Andy Wilkinson - * @author Tommy Ludwig - * @author Eddú Meléndez - * @author Stephane Nicoll - * @author Vedran Pavic - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass({ RedisTemplate.class, RedisIndexedSessionRepository.class }) -@ConditionalOnMissingBean(SessionRepository.class) -@ConditionalOnBean(RedisConnectionFactory.class) -@EnableConfigurationProperties(RedisSessionProperties.class) -class RedisSessionConfiguration { - - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "default", - matchIfMissing = true) - @Import(RedisHttpSessionConfiguration.class) - static class DefaultRedisSessionConfiguration { - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - SessionRepositoryCustomizer springBootSessionRepositoryCustomizer( - SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, - ServerProperties serverProperties) { - String cleanupCron = redisSessionProperties.getCleanupCron(); - if (cleanupCron != null) { - throw new InvalidConfigurationPropertyValueException("spring.session.redis.cleanup-cron", cleanupCron, - "Cron-based cleanup is only supported when " - + "spring.session.redis.repository-type is set to indexed."); - } - return (sessionRepository) -> { - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(sessionProperties - .determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); - map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); - map.from(redisSessionProperties::getFlushMode).to(sessionRepository::setFlushMode); - map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); - }; - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "indexed") - @Import(RedisIndexedHttpSessionConfiguration.class) - static class IndexedRedisSessionConfiguration { - - @Bean - @ConditionalOnMissingBean - ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) { - return switch (redisSessionProperties.getConfigureAction()) { - case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsAction(); - case NONE -> ConfigureRedisAction.NO_OP; - }; - } - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - SessionRepositoryCustomizer springBootSessionRepositoryCustomizer( - SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, - ServerProperties serverProperties) { - return (sessionRepository) -> { - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - map.from(sessionProperties - .determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); - map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); - map.from(redisSessionProperties::getFlushMode).to(sessionRepository::setFlushMode); - map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); - map.from(redisSessionProperties::getCleanupCron).to(sessionRepository::setCleanupCron); - }; - } - - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/additional-spring-configuration-metadata.json index c58e3fc7a5..6cbe8f2cc1 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -460,18 +460,6 @@ "description": "Whether to enable Servlet HTTP encoding support.", "defaultValue": true }, - { - "name": "spring.session.redis.cleanup-cron", - "defaultValue": "0 * * * * *" - }, - { - "name": "spring.session.servlet.filter-dispatcher-types", - "defaultValue": [ - "async", - "error", - "request" - ] - }, { "name": "spring.sql.init.enabled", "type": "java.lang.Boolean", diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index b1b645aee1..372ba7847c 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -17,7 +17,6 @@ org.springframework.boot.autoconfigure.http.client.reactive.ClientHttpConnectorA org.springframework.boot.autoconfigure.http.client.reactive.service.ReactiveHttpServiceClientAutoConfiguration org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration -org.springframework.boot.autoconfigure.session.SessionAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/web/servlet/FilterOrderingIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/web/servlet/FilterOrderingIntegrationTests.java index ccd3fc53a6..4381ddf4f5 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/web/servlet/FilterOrderingIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/web/servlet/FilterOrderingIntegrationTests.java @@ -24,9 +24,9 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration; import org.springframework.boot.http.autoconfigure.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.security.autoconfigure.servlet.SecurityAutoConfiguration; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor; import org.springframework.boot.web.server.servlet.MockServletWebServer.RegisteredFilter; diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 3511e842a5..0459872639 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -2043,6 +2043,11 @@ bom { "spring-boot-security-oauth2-client", "spring-boot-security-oauth2-resource-server", "spring-boot-sendgrid", + "spring-boot-session", + "spring-boot-session-data-mongodb", + "spring-boot-session-data-redis", + "spring-boot-session-hazelcast", + "spring-boot-session-jdbc", "spring-boot-starter", "spring-boot-starter-activemq", "spring-boot-starter-actuator", diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index 5a5b7ab8f0..bae6683c97 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -118,6 +118,11 @@ dependencies { autoConfiguration(project(path: ":spring-boot-project:spring-boot-security-oauth2-resource-server", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-security-saml2", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-sendgrid", configuration: "autoConfigurationMetadata")) + autoConfiguration(project(path: ":spring-boot-project:spring-boot-session", configuration: "autoConfigurationMetadata")) + autoConfiguration(project(path: ":spring-boot-project:spring-boot-session-data-mongodb", configuration: "autoConfigurationMetadata")) + autoConfiguration(project(path: ":spring-boot-project:spring-boot-session-data-redis", configuration: "autoConfigurationMetadata")) + autoConfiguration(project(path: ":spring-boot-project:spring-boot-session-hazelcast", configuration: "autoConfigurationMetadata")) + autoConfiguration(project(path: ":spring-boot-project:spring-boot-session-jdbc", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-testcontainers", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-thymeleaf", configuration: "autoConfigurationMetadata")) autoConfiguration(project(path: ":spring-boot-project:spring-boot-tomcat", configuration: "autoConfigurationMetadata")) @@ -188,6 +193,11 @@ dependencies { configurationProperties(project(path: ":spring-boot-project:spring-boot-security-oauth2-resource-server", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-security-saml2", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-sendgrid", configuration: "configurationPropertiesMetadata")) + configurationProperties(project(path: ":spring-boot-project:spring-boot-session", configuration: "configurationPropertiesMetadata")) + configurationProperties(project(path: ":spring-boot-project:spring-boot-session-data-mongodb", configuration: "configurationPropertiesMetadata")) + configurationProperties(project(path: ":spring-boot-project:spring-boot-session-data-redis", configuration: "configurationPropertiesMetadata")) + configurationProperties(project(path: ":spring-boot-project:spring-boot-session-hazelcast", configuration: "configurationPropertiesMetadata")) + configurationProperties(project(path: ":spring-boot-project:spring-boot-session-jdbc", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-test-autoconfigure", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-testcontainers", configuration: "configurationPropertiesMetadata")) configurationProperties(project(path: ":spring-boot-project:spring-boot-thymeleaf", configuration: "configurationPropertiesMetadata")) diff --git a/spring-boot-project/spring-boot-session-data-mongodb/build.gradle b/spring-boot-project/spring-boot-session-data-mongodb/build.gradle new file mode 100644 index 0000000000..727870c551 --- /dev/null +++ b/spring-boot-project/spring-boot-session-data-mongodb/build.gradle @@ -0,0 +1,33 @@ +plugins { + id "java-library" + id "org.springframework.boot.auto-configuration" + id "org.springframework.boot.configuration-properties" + id "org.springframework.boot.deployed" + id "org.springframework.boot.docker-test" + id "org.springframework.boot.optional-dependencies" +} + +description = "Spring Boot Session Data MongoDB" + +dependencies { + api(project(":spring-boot-project:spring-boot")) + api(project(":spring-boot-project:spring-boot-session")) + api("org.springframework.session:spring-session-data-mongodb") + + implementation(project(":spring-boot-project:spring-boot-data-mongodb")) + + optional(project(":spring-boot-project:spring-boot-autoconfigure")) + optional(project(":spring-boot-project:spring-boot-webflux")) + + dockerTestImplementation(project(":spring-boot-project:spring-boot-test")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot"))) + dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot-session"))) + dockerTestImplementation("org.mongodb:mongodb-driver-reactivestreams") + dockerTestImplementation("org.mongodb:mongodb-driver-sync") + dockerTestImplementation("org.testcontainers:junit-jupiter") + dockerTestImplementation("org.testcontainers:mongodb") + + dockerTestRuntimeOnly("ch.qos.logback:logback-classic") + dockerTestRuntimeOnly("jakarta.servlet:jakarta.servlet-api") +} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java b/spring-boot-project/spring-boot-session-data-mongodb/src/dockerTest/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoReactiveSessionAutoConfigurationTests.java similarity index 77% rename from spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java rename to spring-boot-project/spring-boot-session-data-mongodb/src/dockerTest/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoReactiveSessionAutoConfigurationTests.java index 9adf680a04..734c58c74e 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationMongoTests.java +++ b/spring-boot-project/spring-boot-session-data-mongodb/src/dockerTest/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoReactiveSessionAutoConfigurationTests.java @@ -14,22 +14,22 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.data.mongodb.autoconfigure; import java.time.Duration; import java.util.List; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration; import org.springframework.boot.data.mongodb.autoconfigure.MongoReactiveDataAutoConfiguration; -import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration; import org.springframework.boot.mongodb.autoconfigure.MongoReactiveAutoConfiguration; -import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.session.autoconfigure.AbstractReactiveSessionAutoConfigurationTests; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; @@ -38,27 +38,27 @@ import org.springframework.boot.webflux.autoconfigure.WebSessionIdResolverAutoCo import org.springframework.http.ResponseCookie; import org.springframework.session.MapSession; import org.springframework.session.data.mongo.ReactiveMongoSessionRepository; -import org.springframework.session.data.redis.ReactiveRedisSessionRepository; import static org.assertj.core.api.Assertions.assertThat; /** - * Mongo-specific tests for {@link SessionAutoConfiguration}. + * Tests for the reactive support of {@link MongoSessionAutoConfiguration}. * * @author Andy Wilkinson * @author Weix Sun */ @Testcontainers(disabledWithoutDocker = true) -class ReactiveSessionAutoConfigurationMongoTests extends AbstractSessionAutoConfigurationTests { +class MongoReactiveSessionAutoConfigurationTests extends AbstractReactiveSessionAutoConfigurationTests { @Container static final MongoDBContainer mongoDb = TestImage.container(MongoDBContainer.class); - private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() - .withClassLoader(new FilteredClassLoader(ReactiveRedisSessionRepository.class)) - .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class, MongoAutoConfiguration.class, - MongoDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class, - MongoReactiveDataAutoConfiguration.class)); + @BeforeEach + void prepareContextRunner() { + this.contextRunner = new ReactiveWebApplicationContextRunner().withConfiguration( + AutoConfigurations.of(SessionAutoConfiguration.class, MongoSessionAutoConfiguration.class, + MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class)); + } @Test void defaultConfig() { @@ -99,12 +99,8 @@ class ReactiveSessionAutoConfigurationMongoTests extends AbstractSessionAutoConf @Test void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() { - AutoConfigurations autoConfigurations = AutoConfigurations.of(SessionAutoConfiguration.class, - MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class, - MongoReactiveDataAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class); - new ReactiveWebApplicationContextRunner().withConfiguration(autoConfigurations) - .withUserConfiguration(Config.class) - .withClassLoader(new FilteredClassLoader(ReactiveRedisSessionRepository.class)) + this.contextRunner.withConfiguration(AutoConfigurations.of(WebSessionIdResolverAutoConfiguration.class)) + .withUserConfiguration(ReactiveWebServerConfiguration.class) .withPropertyValues("server.reactive.session.cookie.name:JSESSIONID", "server.reactive.session.cookie.domain:.example.com", "server.reactive.session.cookie.path:/example", "server.reactive.session.cookie.max-age:60", diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationMongoTests.java b/spring-boot-project/spring-boot-session-data-mongodb/src/dockerTest/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoSessionAutoConfigurationTests.java similarity index 79% rename from spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationMongoTests.java rename to spring-boot-project/spring-boot-session-data-mongodb/src/dockerTest/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoSessionAutoConfigurationTests.java index 4d5d33e009..901bda49b7 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationMongoTests.java +++ b/spring-boot-project/spring-boot-session-data-mongodb/src/dockerTest/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoSessionAutoConfigurationTests.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.data.mongodb.autoconfigure; import java.time.Duration; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.junit.jupiter.Container; @@ -27,7 +28,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration; import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration; -import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.session.autoconfigure.AbstractSessionAutoConfigurationTests; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -36,29 +38,27 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.config.SessionRepositoryCustomizer; import org.springframework.session.data.mongo.MongoIndexedSessionRepository; -import org.springframework.session.data.redis.RedisIndexedSessionRepository; -import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; -import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import static org.assertj.core.api.Assertions.assertThat; /** - * Mongo-specific tests for {@link SessionAutoConfiguration}. + * Tests for the servlet support of {@link MongoSessionAutoConfiguration}. * * @author Andy Wilkinson */ @Testcontainers(disabledWithoutDocker = true) -class SessionAutoConfigurationMongoTests extends AbstractSessionAutoConfigurationTests { +class MongoSessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests { @Container static final MongoDBContainer mongoDb = TestImage.container(MongoDBContainer.class); - private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() - .withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class, - JdbcIndexedSessionRepository.class, RedisIndexedSessionRepository.class)) - .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, - SessionAutoConfiguration.class)) - .withPropertyValues("spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl()); + @BeforeEach + void prepareContextRunner() { + this.contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, + SessionAutoConfiguration.class, MongoSessionAutoConfiguration.class)) + .withPropertyValues("spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl()); + } @Test void defaultConfig() { diff --git a/spring-boot-project/spring-boot-session-data-mongodb/src/main/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoSessionAutoConfiguration.java b/spring-boot-project/spring-boot-session-data-mongodb/src/main/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoSessionAutoConfiguration.java new file mode 100644 index 0000000000..f0eb53d507 --- /dev/null +++ b/spring-boot-project/spring-boot-session-data-mongodb/src/main/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoSessionAutoConfiguration.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2025 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 + * + * https://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.session.data.mongodb.autoconfigure; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration; +import org.springframework.boot.data.mongodb.autoconfigure.MongoReactiveDataAutoConfiguration; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; +import org.springframework.boot.session.autoconfigure.SessionProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.ReactiveMongoOperations; +import org.springframework.session.ReactiveSessionRepository; +import org.springframework.session.SessionRepository; +import org.springframework.session.config.ReactiveSessionRepositoryCustomizer; +import org.springframework.session.config.SessionRepositoryCustomizer; +import org.springframework.session.data.mongo.MongoIndexedSessionRepository; +import org.springframework.session.data.mongo.ReactiveMongoSessionRepository; +import org.springframework.session.data.mongo.config.annotation.web.http.MongoHttpSessionConfiguration; +import org.springframework.session.data.mongo.config.annotation.web.reactive.ReactiveMongoWebSessionConfiguration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring Session Data MongoDB. + * + * @author Eddú Meléndez + * @author Stephane Nicoll + * @author Vedran Pavic + * @since 4.0.0 + */ +@AutoConfiguration(before = SessionAutoConfiguration.class, + beforeName = { "org.springframework.boot.webflux.autoconfigure.HttpHandlerAutoConfiguration", + "org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration" }, + after = { MongoDataAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class }, + afterName = "org.springframework.boot.webflux.autoconfigure.WebSessionIdResolverAutoConfiguration") +@EnableConfigurationProperties(MongoSessionProperties.class) +public class MongoSessionAutoConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ MongoOperations.class, MongoIndexedSessionRepository.class }) + @ConditionalOnBean(MongoOperations.class) + @ConditionalOnMissingBean(SessionRepository.class) + @ConditionalOnWebApplication(type = Type.SERVLET) + @Import(MongoHttpSessionConfiguration.class) + class ServletMongoSessionConfiguration { + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + SessionRepositoryCustomizer springBootSessionRepositoryCustomizer( + SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties, + ServerProperties serverProperties) { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + return (sessionRepository) -> { + map.from(sessionProperties + .determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout())) + .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(mongoSessionProperties::getCollectionName).to(sessionRepository::setCollectionName); + }; + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ ReactiveMongoOperations.class, ReactiveMongoSessionRepository.class }) + @ConditionalOnMissingBean(ReactiveSessionRepository.class) + @ConditionalOnBean(ReactiveMongoOperations.class) + @Import(ReactiveMongoWebSessionConfiguration.class) + class ReactiveMongoSessionConfiguration { + + @Bean + ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( + SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties, + ServerProperties serverProperties) { + return (sessionRepository) -> { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(sessionProperties + .determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) + .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(mongoSessionProperties::getCollectionName).to(sessionRepository::setCollectionName); + }; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionProperties.java b/spring-boot-project/spring-boot-session-data-mongodb/src/main/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoSessionProperties.java similarity index 93% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionProperties.java rename to spring-boot-project/spring-boot-session-data-mongodb/src/main/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoSessionProperties.java index 17b7c9068f..7c64425e2e 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/MongoSessionProperties.java +++ b/spring-boot-project/spring-boot-session-data-mongodb/src/main/java/org/springframework/boot/session/data/mongodb/autoconfigure/MongoSessionProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.data.mongodb.autoconfigure; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -22,7 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * Configuration properties for Mongo-backed Spring Session. * * @author Andy Wilkinson - * @since 2.0.0 + * @since 4.0.0 */ @ConfigurationProperties("spring.session.mongodb") public class MongoSessionProperties { diff --git a/spring-boot-project/spring-boot-session-data-mongodb/src/main/java/org/springframework/boot/session/data/mongodb/autoconfigure/package-info.java b/spring-boot-project/spring-boot-session-data-mongodb/src/main/java/org/springframework/boot/session/data/mongodb/autoconfigure/package-info.java new file mode 100644 index 0000000000..d74f7f24af --- /dev/null +++ b/spring-boot-project/spring-boot-session-data-mongodb/src/main/java/org/springframework/boot/session/data/mongodb/autoconfigure/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2025 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 + * + * https://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 Data MongoDB. + */ +package org.springframework.boot.session.data.mongodb.autoconfigure; diff --git a/spring-boot-project/spring-boot-session-data-mongodb/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-session-data-mongodb/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000000..671fd2520c --- /dev/null +++ b/spring-boot-project/spring-boot-session-data-mongodb/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,4 @@ +{ + "groups": [], + "properties": [] +} diff --git a/spring-boot-project/spring-boot-session-data-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-session-data-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..06b2e2944c --- /dev/null +++ b/spring-boot-project/spring-boot-session-data-mongodb/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springframework.boot.session.data.mongodb.autoconfigure.MongoSessionAutoConfiguration diff --git a/spring-boot-project/spring-boot-session-data-redis/build.gradle b/spring-boot-project/spring-boot-session-data-redis/build.gradle new file mode 100644 index 0000000000..8dad8eb350 --- /dev/null +++ b/spring-boot-project/spring-boot-session-data-redis/build.gradle @@ -0,0 +1,37 @@ +plugins { + id "java-library" + id "org.springframework.boot.auto-configuration" + id "org.springframework.boot.configuration-properties" + id "org.springframework.boot.deployed" + id "org.springframework.boot.docker-test" + id "org.springframework.boot.optional-dependencies" +} + +description = "Spring Boot Session Data Redis" + +dependencies { + api(project(":spring-boot-project:spring-boot")) + api(project(":spring-boot-project:spring-boot-session")) + api("org.springframework.session:spring-session-data-redis") + + implementation(project(":spring-boot-project:spring-boot-data-redis")) + + optional(project(":spring-boot-project:spring-boot-autoconfigure")) + optional(project(":spring-boot-project:spring-boot-webflux")) + + dockerTestImplementation(project(":spring-boot-project:spring-boot-test")) + dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot"))) + dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot-session"))) + dockerTestImplementation("com.redis:testcontainers-redis") + dockerTestImplementation("org.testcontainers:junit-jupiter") + + dockerTestRuntimeOnly("jakarta.servlet:jakarta.servlet-api") + + testImplementation(project(":spring-boot-project:spring-boot-test")) + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) + testImplementation("jakarta.servlet:jakarta.servlet-api") + testImplementation("org.springframework:spring-web") + + testRuntimeOnly("ch.qos.logback:logback-classic") +} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java b/spring-boot-project/spring-boot-session-data-redis/src/dockerTest/java/org/springframework/boot/session/data/redis/autoconfigure/ReactiveRedisSessionAutoConfigurationTests.java similarity index 84% rename from spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java rename to spring-boot-project/spring-boot-session-data-redis/src/dockerTest/java/org/springframework/boot/session/data/redis/autoconfigure/ReactiveRedisSessionAutoConfigurationTests.java index c7cab74efb..d20ea50b22 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java +++ b/spring-boot-project/spring-boot-session-data-redis/src/dockerTest/java/org/springframework/boot/session/data/redis/autoconfigure/ReactiveRedisSessionAutoConfigurationTests.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.data.redis.autoconfigure; import java.time.Duration; import java.util.List; import java.util.Map; import com.redis.testcontainers.RedisContainer; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -30,7 +31,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration; import org.springframework.boot.data.redis.autoconfigure.RedisReactiveAutoConfiguration; -import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.session.autoconfigure.AbstractReactiveSessionAutoConfigurationTests; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; @@ -42,7 +44,6 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.http.ResponseCookie; import org.springframework.session.MapSession; import org.springframework.session.SaveMode; -import org.springframework.session.data.mongo.ReactiveMongoSessionRepository; import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository; import org.springframework.session.data.redis.ReactiveRedisSessionRepository; import org.springframework.session.data.redis.config.ConfigureReactiveRedisAction; @@ -52,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; /** - * Reactive Redis-specific tests for {@link SessionAutoConfiguration}. + * Tests for the reactive support of {@link RedisSessionAutoConfiguration}. * * @author Stephane Nicoll * @author Andy Wilkinson @@ -60,30 +61,24 @@ import static org.assertj.core.api.Assertions.entry; * @author Weix Sun */ @Testcontainers(disabledWithoutDocker = true) -class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConfigurationTests { +class ReactiveRedisSessionAutoConfigurationTests extends AbstractReactiveSessionAutoConfigurationTests { @Container public static RedisContainer redis = TestImage.container(RedisContainer.class); - protected final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() - .withClassLoader(new FilteredClassLoader(ReactiveMongoSessionRepository.class)) - .withConfiguration( - AutoConfigurations.of(SessionAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class, - RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class)); + @BeforeEach + void prepareRunner() { + this.contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class, + WebSessionIdResolverAutoConfiguration.class, RedisSessionAutoConfiguration.class, + RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class)); + } @Test void defaultConfig() { this.contextRunner.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE)); } - @Test - void redisTakesPrecedenceMultipleImplementations() { - ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner().withConfiguration( - AutoConfigurations.of(SessionAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class, - RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class)); - contextRunner.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE)); - } - @Test void defaultConfigWithCustomTimeout() { this.contextRunner.withPropertyValues("spring.session.timeout=1m").run((context) -> { @@ -111,7 +106,7 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf @Test void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() { - this.contextRunner.withUserConfiguration(Config.class) + this.contextRunner.withUserConfiguration(ReactiveWebServerConfiguration.class) .withPropertyValues("spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort(), "server.reactive.session.cookie.name:JSESSIONID", @@ -136,13 +131,12 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf this.contextRunner .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) - .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) .run(validateSpringSessionUsesIndexedRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE)); } @Test void indexedRedisSessionStoreWithCustomizations() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + this.contextRunner .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.session.redis.namespace=foo", "spring.session.redis.save-mode=on-get-attribute", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) @@ -151,7 +145,7 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf @Test void indexedRedisSessionWithConfigureActionNone() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + this.contextRunner .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.session.redis.configure-action=none", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) @@ -160,7 +154,7 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf @Test void indexedRedisSessionWithDefaultConfigureActionNone() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + this.contextRunner .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) .run(validateStrategy(ConfigureNotifyKeyspaceEventsReactiveAction.class, @@ -169,8 +163,7 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf @Test void indexedRedisSessionWithCustomConfigureReactiveRedisActionBean() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) - .withUserConfiguration(MaxEntriesReactiveRedisAction.class) + this.contextRunner.withUserConfiguration(MaxEntriesReactiveRedisAction.class) .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) .run(validateStrategy(MaxEntriesReactiveRedisAction.class, entry("set-max-intset-entries", "1024"))); diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java b/spring-boot-project/spring-boot-session-data-redis/src/dockerTest/java/org/springframework/boot/session/data/redis/autoconfigure/RedisSessionAutoConfigurationTests.java similarity index 83% rename from spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java rename to spring-boot-project/spring-boot-session-data-redis/src/dockerTest/java/org/springframework/boot/session/data/redis/autoconfigure/RedisSessionAutoConfigurationTests.java index d2bac83073..1d03842bf1 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/dockerTest/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java +++ b/spring-boot-project/spring-boot-session-data-redis/src/dockerTest/java/org/springframework/boot/session/data/redis/autoconfigure/RedisSessionAutoConfigurationTests.java @@ -14,12 +14,13 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.data.redis.autoconfigure; import java.time.Duration; import java.util.Map; import com.redis.testcontainers.RedisContainer; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -28,7 +29,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration; -import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.session.autoconfigure.AbstractSessionAutoConfigurationTests; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -40,33 +42,31 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.session.FlushMode; import org.springframework.session.SaveMode; import org.springframework.session.config.SessionRepositoryCustomizer; -import org.springframework.session.data.mongo.MongoIndexedSessionRepository; import org.springframework.session.data.redis.RedisIndexedSessionRepository; import org.springframework.session.data.redis.RedisSessionRepository; import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction; import org.springframework.session.data.redis.config.ConfigureRedisAction; -import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; -import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; /** - * Redis specific tests for {@link SessionAutoConfiguration}. + * Tests for {@link RedisSessionAutoConfiguration}. * * @author Stephane Nicoll * @author Vedran Pavic */ @Testcontainers(disabledWithoutDocker = true) -class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfigurationTests { +class RedisSessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests { @Container public static RedisContainer redis = TestImage.container(RedisContainer.class); - protected final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() - .withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class, - JdbcIndexedSessionRepository.class, MongoIndexedSessionRepository.class)) - .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)); + @BeforeEach + void prepareContextRunner() { + this.contextRunner = new WebApplicationContextRunner().withConfiguration(AutoConfigurations + .of(SessionAutoConfiguration.class, RedisSessionAutoConfiguration.class, RedisAutoConfiguration.class)); + } @Test void defaultConfig() { @@ -90,21 +90,11 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio }); } - @Test - void redisTakesPrecedenceMultipleImplementations() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) - .withPropertyValues("spring.data.redis.host=" + redis.getHost(), - "spring.data.redis.port=" + redis.getFirstMappedPort()) - .run(validateSpringSessionUsesDefaultRedis("spring:session:", FlushMode.ON_SAVE, - SaveMode.ON_SET_ATTRIBUTE)); - } - @Test void defaultConfigWithCustomTimeout() { this.contextRunner .withPropertyValues("spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort(), "spring.session.timeout=1m") - .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) .run((context) -> { RedisSessionRepository repository = validateSessionRepository(context, RedisSessionRepository.class); assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", Duration.ofMinutes(1)); @@ -113,7 +103,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio @Test void defaultRedisSessionStoreWithCustomizations() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + this.contextRunner .withPropertyValues("spring.session.redis.namespace=foo", "spring.session.redis.flush-mode=immediate", "spring.session.redis.save-mode=on-get-attribute", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) @@ -132,7 +122,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio @Test void indexedRedisSessionStoreWithCustomizations() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + this.contextRunner .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.session.redis.namespace=foo", "spring.session.redis.flush-mode=immediate", "spring.session.redis.save-mode=on-get-attribute", "spring.session.redis.cleanup-cron=0 0 12 * * *", "spring.data.redis.host=" + redis.getHost(), @@ -143,7 +133,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio @Test void indexedRedisSessionWithConfigureActionNone() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + this.contextRunner .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.session.redis.configure-action=none", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) @@ -152,7 +142,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio @Test void indexedRedisSessionWithDefaultConfigureActionNone() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + this.contextRunner .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) .run(validateStrategy(ConfigureNotifyKeyspaceEventsAction.class, entry("notify-keyspace-events", "gxE"))); @@ -160,8 +150,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio @Test void indexedRedisSessionWithCustomConfigureRedisActionBean() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) - .withUserConfiguration(MaxEntriesRedisAction.class) + this.contextRunner.withUserConfiguration(MaxEntriesRedisAction.class) .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) .run(validateStrategy(MaxEntriesRedisAction.class, entry("set-max-intset-entries", "1024"))); @@ -170,8 +159,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio @Test void whenTheUserDefinesTheirOwnSessionRepositoryCustomizerThenDefaultConfigurationIsOverwritten() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) - .withUserConfiguration(CustomizerConfiguration.class) + this.contextRunner.withUserConfiguration(CustomizerConfiguration.class) .withPropertyValues("spring.session.redis.flush-mode=immediate", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) .run((context) -> { @@ -182,8 +170,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio @Test void whenIndexedAndTheUserDefinesTheirOwnSessionRepositoryCustomizerThenDefaultConfigurationIsOverwritten() { - this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) - .withUserConfiguration(IndexedCustomizerConfiguration.class) + this.contextRunner.withUserConfiguration(IndexedCustomizerConfiguration.class) .withPropertyValues("spring.session.redis.repository-type=indexed", "spring.session.redis.flush-mode=immediate", "spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort()) diff --git a/spring-boot-project/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/RedisSessionAutoConfiguration.java b/spring-boot-project/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/RedisSessionAutoConfiguration.java new file mode 100644 index 0000000000..9f891ddc93 --- /dev/null +++ b/spring-boot-project/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/RedisSessionAutoConfiguration.java @@ -0,0 +1,211 @@ +/* + * Copyright 2012-2025 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 + * + * https://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.session.data.redis.autoconfigure; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; +import org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration; +import org.springframework.boot.data.redis.autoconfigure.RedisReactiveAutoConfiguration; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; +import org.springframework.boot.session.autoconfigure.SessionProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.session.ReactiveSessionRepository; +import org.springframework.session.SessionRepository; +import org.springframework.session.config.ReactiveSessionRepositoryCustomizer; +import org.springframework.session.config.SessionRepositoryCustomizer; +import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository; +import org.springframework.session.data.redis.ReactiveRedisSessionRepository; +import org.springframework.session.data.redis.RedisIndexedSessionRepository; +import org.springframework.session.data.redis.RedisSessionRepository; +import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction; +import org.springframework.session.data.redis.config.ConfigureReactiveRedisAction; +import org.springframework.session.data.redis.config.ConfigureRedisAction; +import org.springframework.session.data.redis.config.annotation.ConfigureNotifyKeyspaceEventsReactiveAction; +import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration; +import org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration; +import org.springframework.session.data.redis.config.annotation.web.server.RedisIndexedWebSessionConfiguration; +import org.springframework.session.data.redis.config.annotation.web.server.RedisWebSessionConfiguration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring Session Data Redis. + * + * @author Andy Wilkinson + * @author Tommy Ludwig + * @author Eddú Meléndez + * @author Stephane Nicoll + * @author Vedran Pavic + * @since 4.0.0 + */ +@AutoConfiguration(before = SessionAutoConfiguration.class, + beforeName = { "org.springframework.boot.webflux.autoconfigure.HttpHandlerAutoConfiguration", + "org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration" }, + after = { RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class }, + afterName = "org.springframework.boot.webflux.autoconfigure.WebSessionIdResolverAutoConfiguration") +@EnableConfigurationProperties({ RedisSessionProperties.class, ServerProperties.class, SessionProperties.class }) +public class RedisSessionAutoConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ RedisTemplate.class, RedisIndexedSessionRepository.class }) + @ConditionalOnMissingBean(SessionRepository.class) + @ConditionalOnBean(RedisConnectionFactory.class) + @ConditionalOnWebApplication(type = Type.SERVLET) + static class ServletRedisSessionConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "default", + matchIfMissing = true) + @Import(RedisHttpSessionConfiguration.class) + static class DefaultRedisSessionConfiguration { + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + SessionRepositoryCustomizer springBootSessionRepositoryCustomizer( + SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, + ServerProperties serverProperties) { + String cleanupCron = redisSessionProperties.getCleanupCron(); + if (cleanupCron != null) { + throw new InvalidConfigurationPropertyValueException("spring.session.redis.cleanup-cron", + cleanupCron, "Cron-based cleanup is only supported when " + + "spring.session.redis.repository-type is set to indexed."); + } + return (sessionRepository) -> { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(sessionProperties + .determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout())) + .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); + map.from(redisSessionProperties::getFlushMode).to(sessionRepository::setFlushMode); + map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); + }; + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "indexed") + @Import(RedisIndexedHttpSessionConfiguration.class) + static class IndexedRedisSessionConfiguration { + + @Bean + @ConditionalOnMissingBean + ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) { + return switch (redisSessionProperties.getConfigureAction()) { + case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsAction(); + case NONE -> ConfigureRedisAction.NO_OP; + }; + } + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + SessionRepositoryCustomizer springBootSessionRepositoryCustomizer( + SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, + ServerProperties serverProperties) { + return (sessionRepository) -> { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(sessionProperties + .determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout())) + .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); + map.from(redisSessionProperties::getFlushMode).to(sessionRepository::setFlushMode); + map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); + map.from(redisSessionProperties::getCleanupCron).to(sessionRepository::setCleanupCron); + }; + } + + } + + } + + @ConditionalOnWebApplication(type = Type.REACTIVE) + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ ReactiveRedisConnectionFactory.class, ReactiveRedisSessionRepository.class }) + @ConditionalOnMissingBean(ReactiveSessionRepository.class) + @ConditionalOnBean(ReactiveRedisConnectionFactory.class) + class ReactiveRedisSessionConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "default", + matchIfMissing = true) + @Import(RedisWebSessionConfiguration.class) + static class DefaultRedisSessionConfiguration { + + @Bean + ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( + SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, + ServerProperties serverProperties) { + return (sessionRepository) -> { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(sessionProperties + .determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) + .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); + map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); + }; + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "indexed") + @Import(RedisIndexedWebSessionConfiguration.class) + static class IndexedRedisSessionConfiguration { + + @Bean + @ConditionalOnMissingBean + ConfigureReactiveRedisAction configureReactiveRedisAction(RedisSessionProperties redisSessionProperties) { + return switch (redisSessionProperties.getConfigureAction()) { + case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsReactiveAction(); + case NONE -> ConfigureReactiveRedisAction.NO_OP; + }; + } + + @Bean + ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( + SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties, + ServerProperties serverProperties) { + return (sessionRepository) -> { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(sessionProperties + .determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) + .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); + map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); + }; + } + + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.java b/spring-boot-project/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/RedisSessionProperties.java similarity index 97% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.java rename to spring-boot-project/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/RedisSessionProperties.java index d2604b8bb3..8fc37e1645 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/RedisSessionProperties.java +++ b/spring-boot-project/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/RedisSessionProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.data.redis.autoconfigure; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.session.FlushMode; @@ -24,7 +24,7 @@ import org.springframework.session.SaveMode; * Configuration properties for Redis backed Spring Session. * * @author Vedran Pavic - * @since 2.0.0 + * @since 4.0.0 */ @ConfigurationProperties("spring.session.redis") public class RedisSessionProperties { diff --git a/spring-boot-project/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/package-info.java b/spring-boot-project/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/package-info.java new file mode 100644 index 0000000000..51c3a4426a --- /dev/null +++ b/spring-boot-project/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2025 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 + * + * https://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 Data Redis. + */ +package org.springframework.boot.session.data.redis.autoconfigure; diff --git a/spring-boot-project/spring-boot-session-data-redis/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-session-data-redis/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000000..72de9b1f76 --- /dev/null +++ b/spring-boot-project/spring-boot-session-data-redis/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,8 @@ +{ + "properties": [ + { + "name": "spring.session.redis.cleanup-cron", + "defaultValue": "0 * * * * *" + } + ] +} diff --git a/spring-boot-project/spring-boot-session-data-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-session-data-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..be2f677f57 --- /dev/null +++ b/spring-boot-project/spring-boot-session-data-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springframework.boot.session.data.redis.autoconfigure.RedisSessionAutoConfiguration diff --git a/spring-boot-project/spring-boot-session-hazelcast/build.gradle b/spring-boot-project/spring-boot-session-hazelcast/build.gradle new file mode 100644 index 0000000000..e35cbe43ce --- /dev/null +++ b/spring-boot-project/spring-boot-session-hazelcast/build.gradle @@ -0,0 +1,27 @@ +plugins { + id "java-library" + id "org.springframework.boot.auto-configuration" + id "org.springframework.boot.configuration-properties" + id "org.springframework.boot.deployed" + id "org.springframework.boot.optional-dependencies" +} + +description = "Spring Boot Session Hazelcast" + +dependencies { + api(project(":spring-boot-project:spring-boot")) + api(project(":spring-boot-project:spring-boot-session")) + api("org.springframework.session:spring-session-hazelcast") + + implementation(project(":spring-boot-project:spring-boot-hazelcast")) + + optional(project(":spring-boot-project:spring-boot-autoconfigure")) + + testImplementation(project(":spring-boot-project:spring-boot-test")) + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) + testImplementation(testFixtures(project(":spring-boot-project:spring-boot-session"))) + testImplementation("jakarta.servlet:jakarta.servlet-api") + testImplementation("org.springframework:spring-web") + + testRuntimeOnly("ch.qos.logback:logback-classic") +} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java b/spring-boot-project/spring-boot-session-hazelcast/src/main/java/org/springframework/boot/session/hazelcast/autoconfigure/HazelcastSessionAutoConfiguration.java similarity index 74% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java rename to spring-boot-project/spring-boot-session-hazelcast/src/main/java/org/springframework/boot/session/hazelcast/autoconfigure/HazelcastSessionAutoConfiguration.java index 4a3432467c..579f3b9957 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionConfiguration.java +++ b/spring-boot-project/spring-boot-session-hazelcast/src/main/java/org/springframework/boot/session/hazelcast/autoconfigure/HazelcastSessionAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 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. @@ -14,18 +14,23 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.hazelcast.autoconfigure; import com.hazelcast.core.HazelcastInstance; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; +import org.springframework.boot.session.autoconfigure.SessionProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -35,20 +40,22 @@ import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; import org.springframework.session.hazelcast.config.annotation.web.http.HazelcastHttpSessionConfiguration; /** - * Hazelcast backed session configuration. + * {@link EnableAutoConfiguration Auto-configuration} for Spring Session Hazelcast. * * @author Tommy Ludwig * @author Eddú Meléndez * @author Stephane Nicoll * @author Vedran Pavic + * @since 4.0.0 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration(before = SessionAutoConfiguration.class) +@ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass(HazelcastIndexedSessionRepository.class) @ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnBean(HazelcastInstance.class) -@EnableConfigurationProperties(HazelcastSessionProperties.class) +@EnableConfigurationProperties({ HazelcastSessionProperties.class, ServerProperties.class, SessionProperties.class }) @Import(HazelcastHttpSessionConfiguration.class) -class HazelcastSessionConfiguration { +public class HazelcastSessionAutoConfiguration { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionProperties.java b/spring-boot-project/spring-boot-session-hazelcast/src/main/java/org/springframework/boot/session/hazelcast/autoconfigure/HazelcastSessionProperties.java similarity index 95% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionProperties.java rename to spring-boot-project/spring-boot-session-hazelcast/src/main/java/org/springframework/boot/session/hazelcast/autoconfigure/HazelcastSessionProperties.java index b51a626a59..12dc3fe2f6 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/HazelcastSessionProperties.java +++ b/spring-boot-project/spring-boot-session-hazelcast/src/main/java/org/springframework/boot/session/hazelcast/autoconfigure/HazelcastSessionProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.hazelcast.autoconfigure; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.session.FlushMode; @@ -24,7 +24,7 @@ import org.springframework.session.SaveMode; * Configuration properties for Hazelcast backed Spring Session. * * @author Vedran Pavic - * @since 2.0.0 + * @since 4.0.0 */ @ConfigurationProperties("spring.session.hazelcast") public class HazelcastSessionProperties { diff --git a/spring-boot-project/spring-boot-session-hazelcast/src/main/java/org/springframework/boot/session/hazelcast/autoconfigure/package-info.java b/spring-boot-project/spring-boot-session-hazelcast/src/main/java/org/springframework/boot/session/hazelcast/autoconfigure/package-info.java new file mode 100644 index 0000000000..f7caff4ed4 --- /dev/null +++ b/spring-boot-project/spring-boot-session-hazelcast/src/main/java/org/springframework/boot/session/hazelcast/autoconfigure/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2025 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 + * + * https://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 Hazelcast. + */ +package org.springframework.boot.session.hazelcast.autoconfigure; diff --git a/spring-boot-project/spring-boot-session-hazelcast/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-session-hazelcast/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000000..671fd2520c --- /dev/null +++ b/spring-boot-project/spring-boot-session-hazelcast/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,4 @@ +{ + "groups": [], + "properties": [] +} diff --git a/spring-boot-project/spring-boot-session-hazelcast/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-session-hazelcast/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..09d2ab4238 --- /dev/null +++ b/spring-boot-project/spring-boot-session-hazelcast/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springframework.boot.session.hazelcast.autoconfigure.HazelcastSessionAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationHazelcastTests.java b/spring-boot-project/spring-boot-session-hazelcast/src/test/java/org/springframework/boot/session/hazelcast/autoconfigure/HazelcastSessionAutoConfigurationTests.java similarity index 71% rename from spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationHazelcastTests.java rename to spring-boot-project/spring-boot-session-hazelcast/src/test/java/org/springframework/boot/session/hazelcast/autoconfigure/HazelcastSessionAutoConfigurationTests.java index 6ee21e1606..f7e170bddf 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationHazelcastTests.java +++ b/spring-boot-project/spring-boot-session-hazelcast/src/test/java/org/springframework/boot/session/hazelcast/autoconfigure/HazelcastSessionAutoConfigurationTests.java @@ -14,28 +14,26 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.hazelcast.autoconfigure; import java.time.Duration; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.map.IMap; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.test.context.FilteredClassLoader; -import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; +import org.springframework.boot.session.autoconfigure.AbstractSessionAutoConfigurationTests; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.FlushMode; import org.springframework.session.SaveMode; import org.springframework.session.config.SessionRepositoryCustomizer; -import org.springframework.session.data.mongo.MongoIndexedSessionRepository; -import org.springframework.session.data.redis.RedisIndexedSessionRepository; import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; -import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -43,29 +41,30 @@ import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; /** - * Hazelcast specific tests for {@link SessionAutoConfiguration}. + * Tests for {@link HazelcastSessionAutoConfiguration}. * * @author Vedran Pavic */ -class SessionAutoConfigurationHazelcastTests extends AbstractSessionAutoConfigurationTests { +class HazelcastSessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests { - private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() - .withClassLoader(new FilteredClassLoader(JdbcIndexedSessionRepository.class, - RedisIndexedSessionRepository.class, MongoIndexedSessionRepository.class)) - .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)) - .withUserConfiguration(HazelcastConfiguration.class); - - @Test - void defaultConfig() { - this.contextRunner.run(this::validateDefaultConfig); + @BeforeEach + void prepareContextRunner() { + this.contextRunner = new WebApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(SessionAutoConfiguration.class, HazelcastSessionAutoConfiguration.class)) + .withUserConfiguration(HazelcastConfiguration.class); } @Test - void hazelcastTakesPrecedenceOverMongo() { - this.contextRunner - .withClassLoader( - new FilteredClassLoader(RedisIndexedSessionRepository.class, JdbcIndexedSessionRepository.class)) - .run(this::validateDefaultConfig); + void defaultConfig() { + this.contextRunner.run((context) -> { + HazelcastIndexedSessionRepository repository = validateSessionRepository(context, + HazelcastIndexedSessionRepository.class); + assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", + new ServerProperties().getServlet().getSession().getTimeout()); + HazelcastInstance hazelcastInstance = context.getBean(HazelcastInstance.class); + then(hazelcastInstance).should().getMap("spring:session:sessions"); + }); } @Test @@ -77,15 +76,6 @@ class SessionAutoConfigurationHazelcastTests extends AbstractSessionAutoConfigur }); } - private void validateDefaultConfig(AssertableWebApplicationContext context) { - HazelcastIndexedSessionRepository repository = validateSessionRepository(context, - HazelcastIndexedSessionRepository.class); - assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", - new ServerProperties().getServlet().getSession().getTimeout()); - HazelcastInstance hazelcastInstance = context.getBean(HazelcastInstance.class); - then(hazelcastInstance).should().getMap("spring:session:sessions"); - } - @Test void customMapName() { this.contextRunner.withPropertyValues("spring.session.hazelcast.map-name=foo:bar:biz").run((context) -> { diff --git a/spring-boot-project/spring-boot-session-jdbc/build.gradle b/spring-boot-project/spring-boot-session-jdbc/build.gradle new file mode 100644 index 0000000000..46ce7e9122 --- /dev/null +++ b/spring-boot-project/spring-boot-session-jdbc/build.gradle @@ -0,0 +1,32 @@ +plugins { + id "java-library" + id "org.springframework.boot.auto-configuration" + id "org.springframework.boot.configuration-properties" + id "org.springframework.boot.deployed" + id "org.springframework.boot.optional-dependencies" +} + +description = "Spring Boot Session JDBC" + +dependencies { + api(project(":spring-boot-project:spring-boot")) + api(project(":spring-boot-project:spring-boot-session")) + api("org.springframework.session:spring-session-jdbc") + + implementation(project(":spring-boot-project:spring-boot-jdbc")) + + optional(project(":spring-boot-project:spring-boot-autoconfigure")) + + testImplementation(project(":spring-boot-project:spring-boot-test")) + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) + testImplementation(project(":spring-boot-project:spring-boot-flyway")) + testImplementation(project(":spring-boot-project:spring-boot-liquibase")) + testImplementation(testFixtures(project(":spring-boot-project:spring-boot-session"))) + testImplementation("jakarta.servlet:jakarta.servlet-api") + testImplementation("org.apache.commons:commons-dbcp2") + testImplementation("org.springframework:spring-web") + + testRuntimeOnly("ch.qos.logback:logback-classic") + testRuntimeOnly("org.flywaydb:flyway-database-hsqldb") + testRuntimeOnly("org.hsqldb:hsqldb") +} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector.java b/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector.java similarity index 95% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector.java rename to spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector.java index c227a5849d..872426f3f2 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector.java +++ b/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.jdbc.autoconfigure; import java.util.Collections; import java.util.Set; diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java b/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfiguration.java similarity index 81% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java rename to spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfiguration.java index 2fc3790fbb..cf187fe5f7 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.java +++ b/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -14,22 +14,27 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.jdbc.autoconfigure; import javax.sql.DataSource; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.sql.init.OnDatabaseInitializationCondition; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; +import org.springframework.boot.session.autoconfigure.SessionProperties; import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -41,19 +46,21 @@ import org.springframework.session.jdbc.config.annotation.SpringSessionDataSourc import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration; /** - * JDBC backed session configuration. + * {@link EnableAutoConfiguration Auto-configuraion} for Spring Session JDBC. * * @author Eddú Meléndez * @author Stephane Nicoll * @author Vedran Pavic + * @since 4.0.0 */ -@Configuration(proxyBeanMethods = false) +@AutoConfiguration(before = SessionAutoConfiguration.class) +@ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ JdbcTemplate.class, JdbcIndexedSessionRepository.class }) @ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnBean(DataSource.class) -@EnableConfigurationProperties(JdbcSessionProperties.class) +@EnableConfigurationProperties({ JdbcSessionProperties.class, SessionProperties.class }) @Import({ DatabaseInitializationDependencyConfigurer.class, JdbcHttpSessionConfiguration.class }) -class JdbcSessionConfiguration { +public class JdbcSessionAutoConfiguration { @Bean @ConditionalOnMissingBean diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionDataSourceScriptDatabaseInitializer.java b/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionDataSourceScriptDatabaseInitializer.java similarity index 97% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionDataSourceScriptDatabaseInitializer.java rename to spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionDataSourceScriptDatabaseInitializer.java index a5279ecb45..e0b732569f 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionDataSourceScriptDatabaseInitializer.java +++ b/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionDataSourceScriptDatabaseInitializer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.jdbc.autoconfigure; import java.util.List; @@ -34,7 +34,7 @@ import org.springframework.util.StringUtils; * @author Vedran Pavic * @author Andy Wilkinson * @author Phillip Webb - * @since 2.6.0 + * @since 4.0.0 */ public class JdbcSessionDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer { diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionProperties.java b/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionProperties.java similarity index 96% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionProperties.java rename to spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionProperties.java index 07c15888f5..0e76b6174f 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/JdbcSessionProperties.java +++ b/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.jdbc.autoconfigure; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.sql.init.DatabaseInitializationMode; @@ -22,10 +22,10 @@ import org.springframework.session.FlushMode; import org.springframework.session.SaveMode; /** - * Configuration properties for JDBC backed Spring Session. + * Configuration properties for JDBC-backed Spring Session. * * @author Vedran Pavic - * @since 2.0.0 + * @since 4.0.0 */ @ConfigurationProperties("spring.session.jdbc") public class JdbcSessionProperties { diff --git a/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/package-info.java b/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/package-info.java new file mode 100644 index 0000000000..aabbfb78a8 --- /dev/null +++ b/spring-boot-project/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2025 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 + * + * https://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 JDBC. + */ +package org.springframework.boot.session.jdbc.autoconfigure; diff --git a/spring-boot-project/spring-boot-session-jdbc/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-session-jdbc/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000000..671fd2520c --- /dev/null +++ b/spring-boot-project/spring-boot-session-jdbc/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,4 @@ +{ + "groups": [], + "properties": [] +} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-session-jdbc/src/main/resources/META-INF/spring.factories similarity index 52% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/spring.factories rename to spring-boot-project/spring-boot-session-jdbc/src/main/resources/META-INF/spring.factories index 47cfe7757e..74138988b3 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-session-jdbc/src/main/resources/META-INF/spring.factories @@ -1,3 +1,3 @@ # Depends on Database Initialization Detectors org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\ -org.springframework.boot.autoconfigure.session.JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector +org.springframework.boot.session.jdbc.autoconfigure.JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector diff --git a/spring-boot-project/spring-boot-session-jdbc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-session-jdbc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..824964ec89 --- /dev/null +++ b/spring-boot-project/spring-boot-session-jdbc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springframework.boot.session.jdbc.autoconfigure.JdbcSessionAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationJdbcTests.java b/spring-boot-project/spring-boot-session-jdbc/src/test/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfigurationTests.java similarity index 84% rename from spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationJdbcTests.java rename to spring-boot-project/spring-boot-session-jdbc/src/test/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfigurationTests.java index 2484987f2f..099a8003d9 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationJdbcTests.java +++ b/spring-boot-project/spring-boot-session-jdbc/src/test/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfigurationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.jdbc.autoconfigure; import java.time.Duration; @@ -33,13 +33,12 @@ import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerA import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration; +import org.springframework.boot.session.autoconfigure.AbstractSessionAutoConfigurationTests; +import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; import org.springframework.boot.sql.init.DatabaseInitializationMode; import org.springframework.boot.sql.init.DatabaseInitializationSettings; -import org.springframework.boot.test.context.FilteredClassLoader; -import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.testsupport.classpath.resources.WithResource; -import org.springframework.boot.web.servlet.AbstractFilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -47,9 +46,6 @@ import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.session.FlushMode; import org.springframework.session.SaveMode; -import org.springframework.session.data.mongo.MongoIndexedSessionRepository; -import org.springframework.session.data.redis.RedisIndexedSessionRepository; -import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import org.springframework.session.jdbc.PostgreSqlJdbcIndexedSessionRepositoryCustomizer; import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource; @@ -58,55 +54,34 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** - * JDBC specific tests for {@link SessionAutoConfiguration}. + * Tests for {@link JdbcSessionAutoConfiguration}. * * @author Vedran Pavic * @author Stephane Nicoll */ -class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfigurationTests { - - private WebApplicationContextRunner contextRunner; +class JdbcSessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests { @BeforeEach void prepareRunner() { this.contextRunner = new WebApplicationContextRunner() - .withClassLoader(new FilteredClassLoader(Thread.currentThread().getContextClassLoader(), - HazelcastIndexedSessionRepository.class, MongoIndexedSessionRepository.class, - RedisIndexedSessionRepository.class)) .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, JdbcTemplateAutoConfiguration.class, - SessionAutoConfiguration.class)) + SessionAutoConfiguration.class, JdbcSessionAutoConfiguration.class)) .withPropertyValues("spring.datasource.generate-unique-name=true"); } @Test void defaultConfig() { - this.contextRunner.run(this::validateDefaultConfig); - } - - @Test - void jdbcTakesPrecedenceOverMongoAndHazelcast() { - this.contextRunner.withClassLoader(new FilteredClassLoader(RedisIndexedSessionRepository.class)) - .run(this::validateDefaultConfig); - } - - private void validateDefaultConfig(AssertableWebApplicationContext context) { - JdbcIndexedSessionRepository repository = validateSessionRepository(context, - JdbcIndexedSessionRepository.class); - assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", - new ServerProperties().getServlet().getSession().getTimeout()); - assertThat(repository).hasFieldOrPropertyWithValue("tableName", "SPRING_SESSION"); - assertThat(repository).hasFieldOrPropertyWithValue("cleanupCron", "0 * * * * *"); - assertThat(context.getBean(JdbcSessionProperties.class).getInitializeSchema()) - .isEqualTo(DatabaseInitializationMode.EMBEDDED); - assertThat(context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION")).isEmpty(); - } - - @Test - void filterOrderCanBeCustomized() { - this.contextRunner.withPropertyValues("spring.session.servlet.filter-order=123").run((context) -> { - AbstractFilterRegistrationBean registration = context.getBean(AbstractFilterRegistrationBean.class); - assertThat(registration.getOrder()).isEqualTo(123); + this.contextRunner.run((context) -> { + JdbcIndexedSessionRepository repository = validateSessionRepository(context, + JdbcIndexedSessionRepository.class); + assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", + new ServerProperties().getServlet().getSession().getTimeout()); + assertThat(repository).hasFieldOrPropertyWithValue("tableName", "SPRING_SESSION"); + assertThat(repository).hasFieldOrPropertyWithValue("cleanupCron", "0 * * * * *"); + assertThat(context.getBean(JdbcSessionProperties.class).getInitializeSchema()) + .isEqualTo(DatabaseInitializationMode.EMBEDDED); + assertThat(context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION")).isEmpty(); }); } @@ -136,7 +111,7 @@ class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfiguration @Test void customTableName() { this.contextRunner.withPropertyValues("spring.session.jdbc.table-name=FOO_BAR", - "spring.session.jdbc.schema=classpath:org/springframework/boot/autoconfigure/session/custom-schema-h2.sql") + "spring.session.jdbc.schema=classpath:org/springframework/boot/session/jdbc/autoconfigure/custom-schema-h2.sql") .run((context) -> { JdbcIndexedSessionRepository repository = validateSessionRepository(context, JdbcIndexedSessionRepository.class); diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/JdbcSessionDataSourceScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot-session-jdbc/src/test/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionDataSourceScriptDatabaseInitializerTests.java similarity index 96% rename from spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/JdbcSessionDataSourceScriptDatabaseInitializerTests.java rename to spring-boot-project/spring-boot-session-jdbc/src/test/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionDataSourceScriptDatabaseInitializerTests.java index 81c3359c83..995758287a 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/JdbcSessionDataSourceScriptDatabaseInitializerTests.java +++ b/spring-boot-project/spring-boot-session-jdbc/src/test/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionDataSourceScriptDatabaseInitializerTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.jdbc.autoconfigure; import javax.sql.DataSource; diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/test/resources/org/springframework/boot/autoconfigure/session/custom-schema-h2.sql b/spring-boot-project/spring-boot-session-jdbc/src/test/resources/org/springframework/boot/session/jdbc/autoconfigure/custom-schema-h2.sql similarity index 100% rename from spring-boot-project/spring-boot-autoconfigure-all/src/test/resources/org/springframework/boot/autoconfigure/session/custom-schema-h2.sql rename to spring-boot-project/spring-boot-session-jdbc/src/test/resources/org/springframework/boot/session/jdbc/autoconfigure/custom-schema-h2.sql diff --git a/spring-boot-project/spring-boot-session/build.gradle b/spring-boot-project/spring-boot-session/build.gradle new file mode 100644 index 0000000000..db951f9d3f --- /dev/null +++ b/spring-boot-project/spring-boot-session/build.gradle @@ -0,0 +1,32 @@ +plugins { + id "java-library" + id "java-test-fixtures" + id "org.springframework.boot.auto-configuration" + id "org.springframework.boot.configuration-properties" + id "org.springframework.boot.deployed" + id "org.springframework.boot.optional-dependencies" +} + +description = "Spring Boot Session" + +dependencies { + api(project(":spring-boot-project:spring-boot")) + api("org.springframework.session:spring-session-core") + + optional(project(":spring-boot-project:spring-boot-autoconfigure")) + optional("jakarta.servlet:jakarta.servlet-api") + optional("org.springframework.security:spring-security-web") + + testFixturesImplementation(project(":spring-boot-project:spring-boot-test")) + testFixturesImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) + testFixturesImplementation(testFixtures(project(":spring-boot-project:spring-boot"))) + testFixturesImplementation("io.projectreactor:reactor-core") + + testImplementation(project(":spring-boot-project:spring-boot-test")) + testImplementation(project(":spring-boot-project:spring-boot-tomcat")) + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) + testImplementation(project(":spring-boot-project:spring-boot-webflux")) + testImplementation(testFixtures(project(":spring-boot-project:spring-boot"))) + + testRuntimeOnly("ch.qos.logback:logback-classic") +} diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/DefaultCookieSerializerCustomizer.java b/spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/DefaultCookieSerializerCustomizer.java similarity index 93% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/DefaultCookieSerializerCustomizer.java rename to spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/DefaultCookieSerializerCustomizer.java index 197d81cc9e..3df023fd40 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/DefaultCookieSerializerCustomizer.java +++ b/spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/DefaultCookieSerializerCustomizer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.autoconfigure; import org.springframework.session.web.http.DefaultCookieSerializer; @@ -23,7 +23,7 @@ import org.springframework.session.web.http.DefaultCookieSerializer; * {@link DefaultCookieSerializer} configuration. * * @author Vedran Pavic - * @since 2.3.0 + * @since 4.0.0 */ @FunctionalInterface public interface DefaultCookieSerializerCustomizer { diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java b/spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionAutoConfiguration.java similarity index 76% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java rename to spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionAutoConfiguration.java index 62d9b6ee9c..e2a2db95f5 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/SessionAutoConfiguration.java +++ b/spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionAutoConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.autoconfigure; import java.time.Duration; @@ -37,9 +37,7 @@ import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.security.web.authentication.RememberMeServices; -import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.Session; -import org.springframework.session.SessionRepository; import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices; import org.springframework.session.web.http.CookieHttpSessionIdResolver; import org.springframework.session.web.http.CookieSerializer; @@ -55,19 +53,9 @@ import org.springframework.session.web.http.HttpSessionIdResolver; * @author Stephane Nicoll * @author Vedran Pavic * @author Weix Sun - * @since 1.4.0 + * @since 4.0.0 */ -@AutoConfiguration( - beforeName = { "org.springframework.boot.webflux.autoconfigure.HttpHandlerAutoConfiguration", - "org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration" }, - afterName = { "org.springframework.boot.hazelcast.autoconfigure.HazelcastAutoConfiguration", - "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration", - "org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration", - "org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration", - "org.springframework.boot.data.mongodb.autoconfigure.MongoReactiveDataAutoConfiguration", - "org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration", - "org.springframework.boot.data.redis.autoconfigure.RedisReactiveAutoConfiguration", - "org.springframework.boot.autoconfigure.session.WebSessionIdResolverAutoConfiguration" }) +@AutoConfiguration @ConditionalOnClass(Session.class) @ConditionalOnWebApplication @EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class }) @@ -109,22 +97,6 @@ public class SessionAutoConfiguration { } - @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingBean(SessionRepository.class) - @Import({ RedisSessionConfiguration.class, JdbcSessionConfiguration.class, HazelcastSessionConfiguration.class, - MongoSessionConfiguration.class }) - static class ServletSessionRepositoryConfiguration { - - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnWebApplication(type = Type.REACTIVE) - @ConditionalOnMissingBean(ReactiveSessionRepository.class) - @Import({ RedisReactiveSessionConfiguration.class, MongoReactiveSessionConfiguration.class }) - static class ReactiveSessionConfiguration { - } /** diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java b/spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionProperties.java similarity index 97% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java rename to spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionProperties.java index 452d11c71a..6e240811a6 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/SessionProperties.java +++ b/spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.autoconfigure; import java.time.Duration; import java.time.temporal.ChronoUnit; @@ -34,7 +34,7 @@ import org.springframework.session.web.http.SessionRepositoryFilter; * @author Tommy Ludwig * @author Stephane Nicoll * @author Vedran Pavic - * @since 1.4.0 + * @since 4.0.0 */ @ConfigurationProperties("spring.session") public class SessionProperties { @@ -68,7 +68,6 @@ public class SessionProperties { * {@code fallbackTimeout} is used. * @param fallbackTimeout a fallback timeout value if the timeout isn't configured * @return the session timeout - * @since 2.4.0 */ public Duration determineTimeout(Supplier fallbackTimeout) { return (this.timeout != null) ? this.timeout : fallbackTimeout.get(); diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/SessionRepositoryFilterConfiguration.java b/spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionRepositoryFilterConfiguration.java similarity index 97% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/SessionRepositoryFilterConfiguration.java rename to spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionRepositoryFilterConfiguration.java index 80b4a2d76e..59e29a6194 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/SessionRepositoryFilterConfiguration.java +++ b/spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionRepositoryFilterConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.autoconfigure; import java.util.EnumSet; import java.util.stream.Collectors; diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/package-info.java b/spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/package-info.java similarity index 92% rename from spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/package-info.java rename to spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/package-info.java index 3f57d921a5..c697a9dca2 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/main/java/org/springframework/boot/autoconfigure/session/package-info.java +++ b/spring-boot-project/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/package-info.java @@ -17,4 +17,4 @@ /** * Auto-configuration for Spring Session. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.autoconfigure; diff --git a/spring-boot-project/spring-boot-session/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-session/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000000..db8128cf7a --- /dev/null +++ b/spring-boot-project/spring-boot-session/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,12 @@ +{ + "properties": [ + { + "name": "spring.session.servlet.filter-dispatcher-types", + "defaultValue": [ + "async", + "error", + "request" + ] + } + ] +} diff --git a/spring-boot-project/spring-boot-session/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-session/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..d9e8b25a4b --- /dev/null +++ b/spring-boot-project/spring-boot-session/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springframework.boot.session.autoconfigure.SessionAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationEarlyInitializationIntegrationTests.java b/spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationEarlyInitializationIntegrationTests.java similarity index 97% rename from spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationEarlyInitializationIntegrationTests.java rename to spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationEarlyInitializationIntegrationTests.java index 41da362b47..b70c07d9d8 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationEarlyInitializationIntegrationTests.java +++ b/spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationEarlyInitializationIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.autoconfigure; import java.util.LinkedHashMap; diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java b/spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationTests.java similarity index 83% rename from spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java rename to spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationTests.java index aa79e77374..ba22e2f1f4 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.autoconfigure; import java.util.Collections; @@ -26,22 +26,13 @@ import org.mockito.InOrder; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.FilteredClassLoader; -import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.servlet.AbstractFilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.session.MapSessionRepository; -import org.springframework.session.ReactiveMapSessionRepository; -import org.springframework.session.SessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; -import org.springframework.session.config.annotation.web.server.EnableSpringWebSession; -import org.springframework.session.data.mongo.MongoIndexedSessionRepository; -import org.springframework.session.data.redis.RedisIndexedSessionRepository; -import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; -import org.springframework.session.jdbc.JdbcIndexedSessionRepository; import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices; import org.springframework.session.web.http.CookieHttpSessionIdResolver; import org.springframework.session.web.http.DefaultCookieSerializer; @@ -63,39 +54,11 @@ import static org.mockito.Mockito.mock; * @author Stephane Nicoll * @author Vedran Pavic */ -class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests { +class SessionAutoConfigurationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)); - @Test - void autoConfigurationDisabledIfNoImplementationMatches() { - this.contextRunner - .withClassLoader(new FilteredClassLoader(RedisIndexedSessionRepository.class, - HazelcastIndexedSessionRepository.class, JdbcIndexedSessionRepository.class, - MongoIndexedSessionRepository.class)) - .run((context) -> assertThat(context).doesNotHaveBean(SessionRepository.class)); - } - - @Test - void backOffIfSessionRepositoryIsPresent() { - this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class).run((context) -> { - MapSessionRepository repository = validateSessionRepository(context, MapSessionRepository.class); - assertThat(context).getBean("mySessionRepository").isSameAs(repository); - }); - } - - @Test - void backOffIfReactiveSessionRepositoryIsPresent() { - ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)); - contextRunner.withUserConfiguration(ReactiveSessionRepositoryConfiguration.class).run((context) -> { - ReactiveMapSessionRepository repository = validateSessionRepository(context, - ReactiveMapSessionRepository.class); - assertThat(context).getBean("mySessionRepository").isSameAs(repository); - }); - } - @Test void filterIsRegisteredWithAsyncErrorAndRequestDispatcherTypes() { this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class).run((context) -> { @@ -247,17 +210,6 @@ class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTest } - @Configuration(proxyBeanMethods = false) - @EnableSpringWebSession - static class ReactiveSessionRepositoryConfiguration { - - @Bean - ReactiveMapSessionRepository mySessionRepository() { - return new ReactiveMapSessionRepository(Collections.emptyMap()); - } - - } - @EnableConfigurationProperties(ServerProperties.class) static class ServerPropertiesConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationWithoutSecurityTests.java b/spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationWithoutSecurityTests.java similarity index 73% rename from spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationWithoutSecurityTests.java rename to spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationWithoutSecurityTests.java index 956afde4de..f7737e84a0 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationWithoutSecurityTests.java +++ b/spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationWithoutSecurityTests.java @@ -14,13 +14,19 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.autoconfigure; + +import java.util.Collections; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; import org.springframework.session.web.http.DefaultCookieSerializer; import static org.assertj.core.api.Assertions.assertThat; @@ -32,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Vedran Pavic */ @ClassPathExclusions("spring-security-*") -class SessionAutoConfigurationWithoutSecurityTests extends AbstractSessionAutoConfigurationTests { +class SessionAutoConfigurationWithoutSecurityTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)); @@ -45,4 +51,15 @@ class SessionAutoConfigurationWithoutSecurityTests extends AbstractSessionAutoCo }); } + @Configuration(proxyBeanMethods = false) + @EnableSpringHttpSession + static class SessionRepositoryConfiguration { + + @Bean + MapSessionRepository mySessionRepository() { + return new MapSessionRepository(Collections.emptyMap()); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionPropertiesTests.java b/spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionPropertiesTests.java similarity index 96% rename from spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionPropertiesTests.java rename to spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionPropertiesTests.java index 4dc24f60aa..a21593dfc2 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/SessionPropertiesTests.java +++ b/spring-boot-project/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionPropertiesTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.autoconfigure; import java.time.Duration; import java.util.function.Supplier; diff --git a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java b/spring-boot-project/spring-boot-session/src/testFixtures/java/org/springframework/boot/session/autoconfigure/AbstractReactiveSessionAutoConfigurationTests.java similarity index 69% rename from spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java rename to spring-boot-project/spring-boot-session/src/testFixtures/java/org/springframework/boot/session/autoconfigure/AbstractReactiveSessionAutoConfigurationTests.java index c9739839ce..3182f956bd 100644 --- a/spring-boot-project/spring-boot-autoconfigure-all/src/test/java/org/springframework/boot/autoconfigure/session/AbstractSessionAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-session/src/testFixtures/java/org/springframework/boot/session/autoconfigure/AbstractReactiveSessionAutoConfigurationTests.java @@ -14,40 +14,51 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.session; +package org.springframework.boot.session.autoconfigure; import java.util.Collections; import java.util.function.Consumer; +import org.junit.jupiter.api.Test; + import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; -import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext; import org.springframework.boot.web.server.reactive.MockReactiveWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.session.MapSessionRepository; +import org.springframework.session.ReactiveMapSessionRepository; import org.springframework.session.ReactiveSessionRepository; -import org.springframework.session.SessionRepository; -import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; -import org.springframework.session.web.http.SessionRepositoryFilter; +import org.springframework.session.config.annotation.web.server.EnableSpringWebSession; import org.springframework.web.server.WebSession; import org.springframework.web.server.session.WebSessionManager; import static org.assertj.core.api.Assertions.assertThat; /** - * Shared test utilities for {@link SessionAutoConfiguration} tests. + * Base class for Spring Session auto-configuration tests when the backing store is + * reactive. * - * @author Stephane Nicoll - * @author Weix Sun + * @author Andy Wilkinson */ -public abstract class AbstractSessionAutoConfigurationTests { +public abstract class AbstractReactiveSessionAutoConfigurationTests { private static final MockReactiveWebServerFactory mockReactiveWebServerFactory = new MockReactiveWebServerFactory(); + protected ReactiveWebApplicationContextRunner contextRunner; + + @Test + void backOffIfReactiveSessionRepositoryIsPresent() { + this.contextRunner.withUserConfiguration(ReactiveSessionRepositoryConfiguration.class).run((context) -> { + ReactiveMapSessionRepository repository = validateSessionRepository(context, + ReactiveMapSessionRepository.class); + assertThat(context).getBean("mySessionRepository").isSameAs(repository); + }); + } + protected ContextConsumer assertExchangeWithSession( Consumer exchange) { return (context) -> { @@ -61,15 +72,6 @@ public abstract class AbstractSessionAutoConfigurationTests { }; } - protected > T validateSessionRepository(AssertableWebApplicationContext context, - Class type) { - assertThat(context).hasSingleBean(SessionRepositoryFilter.class); - assertThat(context).hasSingleBean(SessionRepository.class); - SessionRepository repository = context.getBean(SessionRepository.class); - assertThat(repository).as("Wrong session repository type").isInstanceOf(type); - return type.cast(repository); - } - protected > T validateSessionRepository( AssertableReactiveWebApplicationContext context, Class type) { assertThat(context).hasSingleBean(WebSessionManager.class); @@ -79,19 +81,8 @@ public abstract class AbstractSessionAutoConfigurationTests { return type.cast(repository); } - @Configuration - @EnableSpringHttpSession - static class SessionRepositoryConfiguration { - - @Bean - MapSessionRepository mySessionRepository() { - return new MapSessionRepository(Collections.emptyMap()); - } - - } - @Configuration(proxyBeanMethods = false) - static class Config { + protected static class ReactiveWebServerConfiguration { @Bean MockReactiveWebServerFactory mockReactiveWebServerFactory() { @@ -100,4 +91,15 @@ public abstract class AbstractSessionAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + @EnableSpringWebSession + static class ReactiveSessionRepositoryConfiguration { + + @Bean + ReactiveMapSessionRepository mySessionRepository() { + return new ReactiveMapSessionRepository(Collections.emptyMap()); + } + + } + } diff --git a/spring-boot-project/spring-boot-session/src/testFixtures/java/org/springframework/boot/session/autoconfigure/AbstractSessionAutoConfigurationTests.java b/spring-boot-project/spring-boot-session/src/testFixtures/java/org/springframework/boot/session/autoconfigure/AbstractSessionAutoConfigurationTests.java new file mode 100644 index 0000000000..31e875a7fc --- /dev/null +++ b/spring-boot-project/spring-boot-session/src/testFixtures/java/org/springframework/boot/session/autoconfigure/AbstractSessionAutoConfigurationTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2025 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 + * + * https://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.session.autoconfigure; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.SessionRepository; +import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; +import org.springframework.session.web.http.SessionRepositoryFilter; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Base class for Spring Session auto-configuration tests when the backing store cannot be + * reactive. + * + * @author Stephane Nicoll + * @author Weix Sun + * @see AbstractReactiveSessionAutoConfigurationTests + */ +public abstract class AbstractSessionAutoConfigurationTests { + + protected WebApplicationContextRunner contextRunner; + + @Test + void backOffIfSessionRepositoryIsPresent() { + this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class).run((context) -> { + MapSessionRepository repository = validateSessionRepository(context, MapSessionRepository.class); + assertThat(context).getBean("mySessionRepository").isSameAs(repository); + }); + } + + protected > T validateSessionRepository(AssertableWebApplicationContext context, + Class type) { + assertThat(context).hasSingleBean(SessionRepositoryFilter.class); + assertThat(context).hasSingleBean(SessionRepository.class); + SessionRepository repository = context.getBean(SessionRepository.class); + assertThat(repository).as("Wrong session repository type").isInstanceOf(type); + return type.cast(repository); + } + + @Configuration + @EnableSpringHttpSession + static class SessionRepositoryConfiguration { + + @Bean + MapSessionRepository mySessionRepository() { + return new MapSessionRepository(Collections.emptyMap()); + } + + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-hazelcast/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-hazelcast/build.gradle index 76d2e2fad7..b369039796 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-hazelcast/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-hazelcast/build.gradle @@ -5,13 +5,10 @@ plugins { description = "Spring Boot Session smoke test" dependencies { - implementation(project(":spring-boot-project:spring-boot-hazelcast")) + implementation(project(":spring-boot-project:spring-boot-session-hazelcast")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) - implementation("org.springframework.session:spring-session-hazelcast") { - exclude group: "javax.annotation", module: "javax.annotation-api" - } testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-jdbc/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-jdbc/build.gradle index a635503c14..14297c86bc 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-jdbc/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-jdbc/build.gradle @@ -5,11 +5,11 @@ plugins { description = "Spring Boot Session JDBC smoke test" dependencies { + implementation(project(":spring-boot-project:spring-boot-session-jdbc")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) runtimeOnly(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jdbc")) - runtimeOnly("org.springframework.session:spring-session-jdbc") runtimeOnly("com.h2database:h2") testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-jdbc/src/test/java/smoketest/session/SampleSessionJdbcApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-jdbc/src/test/java/smoketest/session/SampleSessionJdbcApplicationTests.java index 2d72fc1d83..aea2ae6969 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-jdbc/src/test/java/smoketest/session/SampleSessionJdbcApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-jdbc/src/test/java/smoketest/session/SampleSessionJdbcApplicationTests.java @@ -53,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Madhura Bhave */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = { "server.servlet.session.timeout:2" }) + properties = { "server.servlet.session.timeout:2", "debug=true" }) class SampleSessionJdbcApplicationTests { private static final ClientHttpRequestFactorySettings DONT_FOLLOW_REDIRECTS = ClientHttpRequestFactorySettings diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-mongo/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-mongo/build.gradle index 3e471b3ddd..b9a7cee97d 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-mongo/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-mongo/build.gradle @@ -12,9 +12,9 @@ dependencies { dockerTestImplementation("org.testcontainers:junit-jupiter") dockerTestImplementation("org.testcontainers:mongodb") + implementation(project(":spring-boot-project:spring-boot-session-data-mongodb")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-mongodb")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) - implementation("org.springframework.session:spring-session-data-mongodb") } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-redis/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-redis/build.gradle index 1d3f00ad9a..11b93a81d3 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-redis/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-redis/build.gradle @@ -12,9 +12,9 @@ dependencies { dockerTestImplementation("com.redis:testcontainers-redis") dockerTestImplementation("org.testcontainers:junit-jupiter") + implementation(project(":spring-boot-project:spring-boot-session-data-redis")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-redis")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web")) - implementation("org.springframework.session:spring-session-data-redis") } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-mongo/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-mongo/build.gradle index 463861bc1f..09540c1f18 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-mongo/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-mongo/build.gradle @@ -12,9 +12,9 @@ dependencies { dockerTestImplementation("org.testcontainers:junit-jupiter") dockerTestImplementation("org.testcontainers:mongodb") + implementation(project(":spring-boot-project:spring-boot-session-data-mongodb")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-webflux")) runtimeOnly(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-mongodb-reactive")) - runtimeOnly("org.springframework.session:spring-session-data-mongodb") } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-redis/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-redis/build.gradle index 8915e446fd..be05d41ec3 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-redis/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-session-webflux-redis/build.gradle @@ -12,9 +12,9 @@ dependencies { dockerTestImplementation("com.redis:testcontainers-redis") dockerTestImplementation("org.testcontainers:junit-jupiter") + implementation(project(":spring-boot-project:spring-boot-session-data-redis")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-webflux")) runtimeOnly(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-redis-reactive")) - runtimeOnly("org.springframework.session:spring-session-data-redis") }