Integrate child management context with parent context's lifecycle

Previously, the child management context was created when the
parent context's web server was initialized and it wasn't stopped
or closed until the parent context was closed. This resulted in
the child context being left running when the parent context was
stopped. This would then cause a failure when the parent context
was started again as another web server initialized event would be
received and a second child management context would be started.

This commit updates the initialization of the child management
context to integrate it with the lifecycle of the parent context.
The management context is now created the first time the parent
context is started. It is stopped when the parent context is
stopped and restarted if the parent context is started again.
This lifecycle management is done using a phase that ensures
that the child context is not started until the parent context's
web server has been started.

Fixes gh-38502
This commit is contained in:
Andy Wilkinson 2023-11-22 19:19:38 +00:00
parent de8b304da4
commit 9c68a2ab87
2 changed files with 43 additions and 5 deletions

View File

@ -33,12 +33,13 @@ import org.springframework.boot.actuate.autoconfigure.web.ManagementContextFacto
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.context.WebServerGracefulShutdownLifecycle;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.annotation.AnnotationConfigRegistry;
import org.springframework.context.aot.ApplicationContextAotGenerator;
import org.springframework.context.event.ContextClosedEvent;
@ -55,8 +56,7 @@ import org.springframework.util.Assert;
* @author Andy Wilkinson
* @author Phillip Webb
*/
class ChildManagementContextInitializer
implements ApplicationListener<WebServerInitializedEvent>, BeanRegistrationAotProcessor {
class ChildManagementContextInitializer implements BeanRegistrationAotProcessor, SmartLifecycle {
private final ManagementContextFactory managementContextFactory;
@ -64,6 +64,8 @@ class ChildManagementContextInitializer
private final ApplicationContextInitializer<ConfigurableApplicationContext> applicationContextInitializer;
private volatile ConfigurableApplicationContext managementContext;
ChildManagementContextInitializer(ManagementContextFactory managementContextFactory,
ApplicationContext parentContext) {
this(managementContextFactory, parentContext, null);
@ -79,12 +81,33 @@ class ChildManagementContextInitializer
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
if (event.getApplicationContext().equals(this.parentContext)) {
public void start() {
if (this.managementContext == null) {
ConfigurableApplicationContext managementContext = createManagementContext();
registerBeans(managementContext);
managementContext.refresh();
this.managementContext = managementContext;
}
else {
this.managementContext.start();
}
}
@Override
public void stop() {
if (this.managementContext != null) {
this.managementContext.stop();
}
}
@Override
public boolean isRunning() {
return this.managementContext != null && this.managementContext.isRunning();
}
@Override
public int getPhase() {
return WebServerGracefulShutdownLifecycle.SMART_LIFECYCLE_PHASE + 512;
}
@Override

View File

@ -55,6 +55,21 @@ class ManagementContextAutoConfigurationTests {
.run((context) -> assertThat(output).satisfies(numberOfOccurrences("Tomcat started on port", 2)));
}
@Test
void childManagementContextShouldRestartWhenParentIsStoppedThenStarted(CapturedOutput output) {
WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(
AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(ManagementContextAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class));
contextRunner.withPropertyValues("server.port=0", "management.server.port=0").run((context) -> {
assertThat(output).satisfies(numberOfOccurrences("Tomcat started on port", 2));
context.getSourceApplicationContext().stop();
context.getSourceApplicationContext().start();
assertThat(output).satisfies(numberOfOccurrences("Tomcat started on port", 4));
});
}
@Test
void givenSamePortManagementServerWhenManagementServerAddressIsConfiguredThenContextRefreshFails() {
WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(