Browse Source

Merge pull request #44310 from nosan

* pr/44310:
  Polish contribution
  Catch WebServer stop or destroy exception when context refresh fails

Closes gh-44310
pull/44330/head
Stéphane Nicoll 1 year ago
parent
commit
84998f91a9
  1. 9
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java
  2. 13
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java
  3. 35
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContextTests.java
  4. 46
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContextTests.java

9
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java

@ -68,8 +68,13 @@ public class ReactiveWebServerApplicationContext extends GenericReactiveWebAppli
catch (RuntimeException ex) { catch (RuntimeException ex) {
WebServer webServer = getWebServer(); WebServer webServer = getWebServer();
if (webServer != null) { if (webServer != null) {
webServer.stop(); try {
webServer.destroy(); webServer.stop();
webServer.destroy();
}
catch (RuntimeException stopOrDestroyEx) {
ex.addSuppressed(stopOrDestroyEx);
}
} }
throw ex; throw ex;
} }

13
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContext.java

@ -146,10 +146,15 @@ public class ServletWebServerApplicationContext extends GenericWebApplicationCon
super.refresh(); super.refresh();
} }
catch (RuntimeException ex) { catch (RuntimeException ex) {
WebServer webServer = this.webServer; try {
if (webServer != null) { WebServer webServer = this.webServer;
webServer.stop(); if (webServer != null) {
webServer.destroy(); webServer.stop();
webServer.destroy();
}
}
catch (RuntimeException stopOrDestroyEx) {
ex.addSuppressed(stopOrDestroyEx);
} }
throw ex; throw ex;
} }

35
spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContextTests.java

@ -43,6 +43,7 @@ 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.mockito.BDDMockito.then; import static org.mockito.BDDMockito.then;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
/** /**
@ -133,6 +134,40 @@ class ReactiveWebServerApplicationContextTests {
then(webServer).should().destroy(); then(webServer).should().destroy();
} }
@Test
void whenContextRefreshFailedThenWebServerStopFailedCatchStopException() {
addWebServerFactoryBean();
addHttpHandlerBean();
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class, () -> {
willThrow(new RuntimeException("WebServer has failed to stop")).willCallRealMethod()
.given(this.context.getWebServer())
.stop();
return new RefreshFailure();
}));
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh)
.withStackTraceContaining("WebServer has failed to stop");
WebServer webServer = this.context.getWebServer();
then(webServer).should().stop();
then(webServer).should(times(0)).destroy();
}
@Test
void whenContextRefreshFailedThenWebServerIsStoppedAndDestroyFailedCatchDestroyException() {
addWebServerFactoryBean();
addHttpHandlerBean();
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class, () -> {
willThrow(new RuntimeException("WebServer has failed to destroy")).willCallRealMethod()
.given(this.context.getWebServer())
.destroy();
return new RefreshFailure();
}));
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh)
.withStackTraceContaining("WebServer has failed to destroy");
WebServer webServer = this.context.getWebServer();
then(webServer).should().stop();
then(webServer).should().destroy();
}
@Test @Test
void whenContextIsClosedThenWebServerIsStoppedAndDestroyed() { void whenContextIsClosedThenWebServerIsStoppedAndDestroyed() {
addWebServerFactoryBean(); addWebServerFactoryBean();

46
spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/context/ServletWebServerApplicationContextTests.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"); * 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.
@ -51,6 +51,7 @@ import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.CapturedOutput;
import org.springframework.boot.testsupport.system.OutputCaptureExtension; import org.springframework.boot.testsupport.system.OutputCaptureExtension;
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer; import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean; import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.ServletContextInitializer;
@ -81,6 +82,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then; import static org.mockito.BDDMockito.then;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -211,6 +213,48 @@ class ServletWebServerApplicationContextTests {
assertThat(listener.receivedEvents()).isEmpty(); assertThat(listener.receivedEvents()).isEmpty();
} }
@Test
void whenContextRefreshFailedThenWebServerIsStoppedAndDestroyed() {
addWebServerFactoryBean();
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class));
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh);
WebServer webServer = this.context.getWebServer();
then(webServer).should(times(2)).stop();
then(webServer).should().destroy();
}
@Test
void whenContextRefreshFailedThenWebServerStopFailedCatchStopException() {
addWebServerFactoryBean();
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class, () -> {
willThrow(new RuntimeException("WebServer has failed to stop")).willCallRealMethod()
.given(this.context.getWebServer())
.stop();
return new RefreshFailure();
}));
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh)
.withStackTraceContaining("WebServer has failed to stop");
WebServer webServer = this.context.getWebServer();
then(webServer).should().stop();
then(webServer).should(times(0)).destroy();
}
@Test
void whenContextRefreshFailedThenWebServerIsStoppedAndDestroyFailedCatchDestroyException() {
addWebServerFactoryBean();
this.context.registerBeanDefinition("refreshFailure", new RootBeanDefinition(RefreshFailure.class, () -> {
willThrow(new RuntimeException("WebServer has failed to destroy")).willCallRealMethod()
.given(this.context.getWebServer())
.destroy();
return new RefreshFailure();
}));
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(this.context::refresh)
.withStackTraceContaining("WebServer has failed to destroy");
WebServer webServer = this.context.getWebServer();
then(webServer).should().stop();
then(webServer).should().destroy();
}
@Test @Test
void cannotSecondRefresh() { void cannotSecondRefresh() {
addWebServerFactoryBean(); addWebServerFactoryBean();

Loading…
Cancel
Save