diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java index 7c65d0634f..709da5ec26 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java @@ -63,9 +63,9 @@ public class DocumentConfigurationProperties extends AbstractTask { builder.addSection("core") .withKeyPrefixes("debug", "trace", "logging", "spring.aop", "spring.application", "spring.autoconfigure", "spring.banner", "spring.beaninfo", "spring.codec", "spring.config", - "spring.info", "spring.jmx", "spring.main", "spring.messages", "spring.pid", "spring.profiles", - "spring.quartz", "spring.reactor", "spring.task", "spring.mandatory-file-encoding", "info", - "spring.output.ansi.enabled") + "spring.info", "spring.jmx", "spring.lifecycle", "spring.main", "spring.messages", "spring.pid", + "spring.profiles", "spring.quartz", "spring.reactor", "spring.task", + "spring.mandatory-file-encoding", "info", "spring.output.ansi.enabled") .addSection("mail").withKeyPrefixes("spring.mail", "spring.sendgrid").addSection("cache") .withKeyPrefixes("spring.cache").addSection("server").withKeyPrefixes("server").addSection("web") .withKeyPrefixes("spring.hateoas", "spring.http", "spring.servlet", "spring.jersey", "spring.mvc", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/LifecycleAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/LifecycleAutoConfiguration.java new file mode 100644 index 0000000000..a3ceb5a91f --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/LifecycleAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2020 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.context; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.DefaultLifecycleProcessor; + +/** + * {@link EnableAutoConfiguration Auto-configuration} relating to the application + * context's lifecycle. + * + * @author Andy Wilkinson + * @since 2.3.0 + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(LifecycleProperties.class) +public class LifecycleAutoConfiguration { + + @Bean(name = AbstractApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME) + @ConditionalOnMissingBean(name = AbstractApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME) + public DefaultLifecycleProcessor defaultLifecycleProcessor(LifecycleProperties properties) { + DefaultLifecycleProcessor lifecycleProcessor = new DefaultLifecycleProcessor(); + lifecycleProcessor.setTimeoutPerShutdownPhase(properties.getTimeoutPerShutdownPhase().toMillis()); + return lifecycleProcessor; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/LifecycleProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/LifecycleProperties.java new file mode 100644 index 0000000000..49c706a8c2 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/LifecycleProperties.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2020 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.context; + +import java.time.Duration; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for lifecycle processing. + * + * @author Andy Wilkinson + * @since 2.3.0 + */ +@ConfigurationProperties(prefix = "spring.lifecycle") +public class LifecycleProperties { + + /** + * Timeout for the shutdown of any phase (group of SmartLifecycle beans with the same + * 'phase' value). + */ + private Duration timeoutPerShutdownPhase = Duration.ofSeconds(30); + + public Duration getTimeoutPerShutdownPhase() { + return this.timeoutPerShutdownPhase; + } + + public void setTimeoutPerShutdownPhase(Duration timeoutPerShutdownPhase) { + this.timeoutPerShutdownPhase = timeoutPerShutdownPhase; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index fa783871cc..6526dcdc8b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -26,6 +26,7 @@ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ +org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/LifecycleAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/LifecycleAutoConfigurationTests.java new file mode 100644 index 0000000000..8b80f5ed4c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/LifecycleAutoConfigurationTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2020 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.context; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.DefaultLifecycleProcessor; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LifecycleAutoConfiguration}. + * + * @author Andy Wilkinson + */ +public class LifecycleAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(LifecycleAutoConfiguration.class)); + + @Test + void lifecycleProcessorIsConfiguredWithDefaultTimeout() { + this.contextRunner.run((context) -> { + assertThat(context).hasBean(AbstractApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME); + Object processor = context.getBean(AbstractApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME); + assertThat(processor).extracting("timeoutPerShutdownPhase").isEqualTo(30000L); + }); + } + + @Test + void lifecycleProcessorIsConfiguredWithCustomDefaultTimeout() { + this.contextRunner.withPropertyValues("spring.lifecycle.timeout-per-shutdown-phase=15s").run((context) -> { + assertThat(context).hasBean(AbstractApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME); + Object processor = context.getBean(AbstractApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME); + assertThat(processor).extracting("timeoutPerShutdownPhase").isEqualTo(15000L); + }); + } + + @Test + void whenUserDefinesALifecycleProcessorBeanThenTheAutoConfigurationBacksOff() { + this.contextRunner.withUserConfiguration(LifecycleProcessorConfiguration.class).run((context) -> { + assertThat(context).hasBean(AbstractApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME); + Object processor = context.getBean(AbstractApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME); + assertThat(processor).extracting("timeoutPerShutdownPhase").isEqualTo(5000L); + }); + } + + @Configuration(proxyBeanMethods = false) + static class LifecycleProcessorConfiguration { + + @Bean(name = AbstractApplicationContext.LIFECYCLE_PROCESSOR_BEAN_NAME) + DefaultLifecycleProcessor customLifecycleProcessor() { + DefaultLifecycleProcessor processor = new DefaultLifecycleProcessor(); + processor.setTimeoutPerShutdownPhase(5000); + return processor; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index efd1a39fce..683f1a0b6b 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -3160,6 +3160,13 @@ To enable graceful shutdown, configure the configprop:server.shutdown[] property server.shutdown=graceful ---- +To configure the timeout period, configure the configprop:spring.lifecycle.timeout-per-shutdown-phase[] property, as shown in the following example: + +[source,properties,indent=0,configprops] +---- +spring.lifecycle.timeout-per-shutdown-phase=20s +---- + [[boot-features-rsocket]]