Browse Source

Clean resources in case of unexpected exception

This commit updates AbstractApplicationContext#refresh to handle any
exceptions, not only BeansExceptions.

Closes gh-28878
pull/31455/head
Stéphane Nicoll 2 years ago
parent
commit
9af239c8be
  1. 5
      spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java
  2. 4
      spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java
  3. 2
      spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java
  4. 61
      spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java

5
spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

@ -619,12 +619,11 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
finishRefresh(); finishRefresh();
} }
catch (BeansException ex) { catch (RuntimeException | Error ex ) {
if (logger.isWarnEnabled()) { if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " + logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex); "cancelling refresh attempt: " + ex);
} }
// Destroy already created singletons to avoid dangling resources. // Destroy already created singletons to avoid dangling resources.
destroyBeans(); destroyBeans();
@ -974,7 +973,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* after an exception got thrown. * after an exception got thrown.
* @param ex the exception that led to the cancellation * @param ex the exception that led to the cancellation
*/ */
protected void cancelRefresh(BeansException ex) { protected void cancelRefresh(Throwable ex) {
this.active.set(false); this.active.set(false);
// Reset common introspection caches in Spring's core infrastructure. // Reset common introspection caches in Spring's core infrastructure.

4
spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -136,7 +136,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
} }
@Override @Override
protected void cancelRefresh(BeansException ex) { protected void cancelRefresh(Throwable ex) {
DefaultListableBeanFactory beanFactory = this.beanFactory; DefaultListableBeanFactory beanFactory = this.beanFactory;
if (beanFactory != null) { if (beanFactory != null) {
beanFactory.setSerializationId(null); beanFactory.setSerializationId(null);

2
spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java

@ -297,7 +297,7 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
} }
@Override @Override
protected void cancelRefresh(BeansException ex) { protected void cancelRefresh(Throwable ex) {
this.beanFactory.setSerializationId(null); this.beanFactory.setSerializationId(null);
super.cancelRefresh(ex); super.cancelRefresh(ex);
} }

61
spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java

@ -29,7 +29,11 @@ import org.mockito.ArgumentCaptor;
import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.AbstractFactoryBean; import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
@ -59,6 +63,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.InstanceOfAssertFactories.type; import static org.assertj.core.api.InstanceOfAssertFactories.type;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@ -302,6 +307,39 @@ class GenericApplicationContextTests {
.isEqualTo("pong:foo"); .isEqualTo("pong:foo");
} }
@Test
void refreshWithRuntimeFailureOnBeanCreationDisposeExistingBeans() {
BeanE one = new BeanE();
context.registerBean("one", BeanE.class, () -> one);
context.registerBean("two", BeanE.class, () -> new BeanE() {
@Override
public void afterPropertiesSet() {
throw new IllegalStateException("Expected");
}
});
assertThatThrownBy(context::refresh).isInstanceOf(BeanCreationException.class)
.hasMessageContaining("two");
assertThat(one.initialized).isTrue();
assertThat(one.destroyed).isTrue();
}
@Test
void refreshWithRuntimeFailureOnAfterSingletonInstantiatedDisposeExistingBeans() {
BeanE one = new BeanE();
BeanE two = new BeanE();
context.registerBean("one", BeanE.class, () -> one);
context.registerBean("two", BeanE.class, () -> two);
context.registerBean("int", SmartInitializingSingleton.class, () -> () -> {
throw new IllegalStateException("expected");
});
assertThatThrownBy(context::refresh).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("expected");
assertThat(one.initialized).isTrue();
assertThat(two.initialized).isTrue();
assertThat(one.destroyed).isTrue();
assertThat(two.destroyed).isTrue();
}
@Test @Test
void refreshForAotSetsContextActive() { void refreshForAotSetsContextActive() {
GenericApplicationContext context = new GenericApplicationContext(); GenericApplicationContext context = new GenericApplicationContext();
@ -646,6 +684,29 @@ class GenericApplicationContextTests {
} }
} }
static class BeanE implements InitializingBean, DisposableBean {
private boolean initialized;
private boolean destroyed;
@Override
public void afterPropertiesSet() throws Exception {
if (initialized) {
throw new IllegalStateException("AfterPropertiesSet called twice");
}
this.initialized = true;
}
@Override
public void destroy() throws Exception {
if (destroyed) {
throw new IllegalStateException("Destroy called twice");
}
this.destroyed = true;
}
}
static class TestAotFactoryBean<T> extends AbstractFactoryBean<T> { static class TestAotFactoryBean<T> extends AbstractFactoryBean<T> {

Loading…
Cancel
Save