From c74af40288faf328271ffa34fbc13a35f27ff848 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 5 Dec 2025 16:14:27 +0100 Subject: [PATCH] Stop already started Lifecycle beans on cancelled refresh Closes gh-35964 --- .../support/AbstractApplicationContext.java | 12 +++- .../ApplicationContextLifecycleTests.java | 64 ++++++++++++++++--- .../support/SmartLifecycleTestBean.java | 26 ++++++++ .../context/support/smartLifecycleTests.xml | 19 ++++++ 4 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 spring-context/src/test/java/org/springframework/context/support/SmartLifecycleTestBean.java create mode 100644 spring-context/src/test/resources/org/springframework/context/support/smartLifecycleTests.xml diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 4afe9f1ba9f..63b6f67f853 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -623,12 +623,22 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader finishRefresh(); } - catch (RuntimeException | Error ex ) { + catch (RuntimeException | Error ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } + // Stop already started Lifecycle beans to avoid dangling resources. + if (this.lifecycleProcessor != null && this.lifecycleProcessor.isRunning()) { + try { + this.lifecycleProcessor.stop(); + } + catch (Throwable ex2) { + logger.warn("Exception thrown from LifecycleProcessor on cancelled refresh", ex2); + } + } + // Destroy already created singletons to avoid dangling resources. destroyBeans(); diff --git a/spring-context/src/test/java/org/springframework/context/support/ApplicationContextLifecycleTests.java b/spring-context/src/test/java/org/springframework/context/support/ApplicationContextLifecycleTests.java index 792a9dfe46b..2482fd8c783 100644 --- a/spring-context/src/test/java/org/springframework/context/support/ApplicationContextLifecycleTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/ApplicationContextLifecycleTests.java @@ -18,17 +18,25 @@ package org.springframework.context.support; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.io.ClassPathResource; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * @author Mark Fisher * @author Chris Beams + * @author Juergen Hoeller */ class ApplicationContextLifecycleTests { @Test void beansStart() { AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass()); + context.start(); LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1"); LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2"); @@ -39,12 +47,14 @@ class ApplicationContextLifecycleTests { assertThat(bean2.isRunning()).as(error).isTrue(); assertThat(bean3.isRunning()).as(error).isTrue(); assertThat(bean4.isRunning()).as(error).isTrue(); + context.close(); } @Test void beansStop() { AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass()); + context.start(); LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1"); LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2"); @@ -55,18 +65,21 @@ class ApplicationContextLifecycleTests { assertThat(bean2.isRunning()).as(startError).isTrue(); assertThat(bean3.isRunning()).as(startError).isTrue(); assertThat(bean4.isRunning()).as(startError).isTrue(); + context.stop(); String stopError = "bean was not stopped"; assertThat(bean1.isRunning()).as(stopError).isFalse(); assertThat(bean2.isRunning()).as(stopError).isFalse(); assertThat(bean3.isRunning()).as(stopError).isFalse(); assertThat(bean4.isRunning()).as(stopError).isFalse(); + context.close(); } @Test void startOrder() { AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass()); + context.start(); LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1"); LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2"); @@ -81,18 +94,22 @@ class ApplicationContextLifecycleTests { assertThat(bean2.getStartOrder()).as(orderError).isGreaterThan(bean1.getStartOrder()); assertThat(bean3.getStartOrder()).as(orderError).isGreaterThan(bean2.getStartOrder()); assertThat(bean4.getStartOrder()).as(orderError).isGreaterThan(bean2.getStartOrder()); + context.close(); } @Test - void stopOrder() { - AbstractApplicationContext context = new ClassPathXmlApplicationContext("lifecycleTests.xml", getClass()); - context.start(); - context.stop(); - LifecycleTestBean bean1 = (LifecycleTestBean) context.getBean("bean1"); - LifecycleTestBean bean2 = (LifecycleTestBean) context.getBean("bean2"); - LifecycleTestBean bean3 = (LifecycleTestBean) context.getBean("bean3"); - LifecycleTestBean bean4 = (LifecycleTestBean) context.getBean("bean4"); + void autoStartup() { + GenericApplicationContext context = new GenericApplicationContext(); + new XmlBeanDefinitionReader(context).loadBeanDefinitions(new ClassPathResource("smartLifecycleTests.xml", getClass())); + + context.refresh(); + LifecycleTestBean bean1 = (LifecycleTestBean) context.getBeanFactory().getBean("bean1"); + LifecycleTestBean bean2 = (LifecycleTestBean) context.getBeanFactory().getBean("bean2"); + LifecycleTestBean bean3 = (LifecycleTestBean) context.getBeanFactory().getBean("bean3"); + LifecycleTestBean bean4 = (LifecycleTestBean) context.getBeanFactory().getBean("bean4"); + + context.close(); String notStoppedError = "bean was not stopped"; assertThat(bean1.getStopOrder()).as(notStoppedError).isGreaterThan(0); assertThat(bean2.getStopOrder()).as(notStoppedError).isGreaterThan(0); @@ -102,7 +119,36 @@ class ApplicationContextLifecycleTests { assertThat(bean2.getStopOrder()).as(orderError).isLessThan(bean1.getStopOrder()); assertThat(bean3.getStopOrder()).as(orderError).isLessThan(bean2.getStopOrder()); assertThat(bean4.getStopOrder()).as(orderError).isLessThan(bean2.getStopOrder()); - context.close(); + } + + @Test + void cancelledRefresh() { + GenericApplicationContext context = new GenericApplicationContext(); + new XmlBeanDefinitionReader(context).loadBeanDefinitions(new ClassPathResource("smartLifecycleTests.xml", getClass())); + context.registerBean(FailingContextRefreshedListener.class); + LifecycleTestBean bean1 = (LifecycleTestBean) context.getBeanFactory().getBean("bean1"); + LifecycleTestBean bean2 = (LifecycleTestBean) context.getBeanFactory().getBean("bean2"); + LifecycleTestBean bean3 = (LifecycleTestBean) context.getBeanFactory().getBean("bean3"); + LifecycleTestBean bean4 = (LifecycleTestBean) context.getBeanFactory().getBean("bean4"); + + assertThatIllegalStateException().isThrownBy(context::refresh); + String notStoppedError = "bean was not stopped"; + assertThat(bean1.getStopOrder()).as(notStoppedError).isGreaterThan(0); + assertThat(bean2.getStopOrder()).as(notStoppedError).isGreaterThan(0); + assertThat(bean3.getStopOrder()).as(notStoppedError).isGreaterThan(0); + assertThat(bean4.getStopOrder()).as(notStoppedError).isGreaterThan(0); + String orderError = "dependent bean must stop before the bean it depends on"; + assertThat(bean2.getStopOrder()).as(orderError).isLessThan(bean1.getStopOrder()); + assertThat(bean3.getStopOrder()).as(orderError).isLessThan(bean2.getStopOrder()); + assertThat(bean4.getStopOrder()).as(orderError).isLessThan(bean2.getStopOrder()); + } + + + private static class FailingContextRefreshedListener implements ApplicationListener { + + public void onApplicationEvent(ContextRefreshedEvent event) { + throw new IllegalStateException(); + } } } diff --git a/spring-context/src/test/java/org/springframework/context/support/SmartLifecycleTestBean.java b/spring-context/src/test/java/org/springframework/context/support/SmartLifecycleTestBean.java new file mode 100644 index 00000000000..74938040898 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/support/SmartLifecycleTestBean.java @@ -0,0 +1,26 @@ +/* + * Copyright 2002-present 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.context.support; + +import org.springframework.context.SmartLifecycle; + +/** + * @author Juergen Hoeller + */ +public class SmartLifecycleTestBean extends LifecycleTestBean implements SmartLifecycle { + +} diff --git a/spring-context/src/test/resources/org/springframework/context/support/smartLifecycleTests.xml b/spring-context/src/test/resources/org/springframework/context/support/smartLifecycleTests.xml new file mode 100644 index 00000000000..24be8565b10 --- /dev/null +++ b/spring-context/src/test/resources/org/springframework/context/support/smartLifecycleTests.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + +