From 3040dc14cd8ac6fface6ab37dcb2d12c5dd8a166 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 18 Nov 2025 12:48:56 -0800 Subject: [PATCH] Apply system environment prefix to management context Update `ManagementContextFactory` so that any system environment prefix set on the parent environment is applied to the child. Closes gh-45858 --- .../web/ManagementContextFactory.java | 12 +++++-- .../ChildManagementContextInitializer.java | 6 +++- ...nagementContextAutoConfigurationTests.java | 36 +++++++++++++++++++ ...ropertySourceEnvironmentPostProcessor.java | 24 ++++++++++++- ...tySourceEnvironmentPostProcessorTests.java | 14 ++++++++ 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/ManagementContextFactory.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/ManagementContextFactory.java index d88a678a9fe..a48c84b7e48 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/ManagementContextFactory.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/ManagementContextFactory.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.ApplicationContextFactory; import org.springframework.boot.WebApplicationType; +import org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor; import org.springframework.boot.web.server.WebServerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -60,8 +61,8 @@ public final class ManagementContextFactory { Environment parentEnvironment = parentContext.getEnvironment(); ConfigurableEnvironment childEnvironment = ApplicationContextFactory.DEFAULT .createEnvironment(this.webApplicationType); - if (parentEnvironment instanceof ConfigurableEnvironment configurableEnvironment) { - childEnvironment.setConversionService((configurableEnvironment).getConversionService()); + if (parentEnvironment instanceof ConfigurableEnvironment configurableParentEnvironment) { + postProcessChildEnvironment(childEnvironment, configurableParentEnvironment); } ConfigurableApplicationContext managementContext = ApplicationContextFactory.DEFAULT .create(this.webApplicationType); @@ -70,6 +71,13 @@ public final class ManagementContextFactory { return managementContext; } + private void postProcessChildEnvironment(ConfigurableEnvironment childEnvironment, + ConfigurableEnvironment parentEnvironment) { + childEnvironment.setConversionService((parentEnvironment).getConversionService()); + SystemEnvironmentPropertySourceEnvironmentPostProcessor.postProcessEnvironment(childEnvironment, + parentEnvironment); + } + public void registerWebServerFactoryBeans(ApplicationContext parentContext, ConfigurableApplicationContext managementContext, AnnotationConfigRegistry registry) { registry.register(this.autoConfigurationClasses); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java index c4fd7c0b57b..533a8b503d7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ChildManagementContextInitializer.java @@ -121,7 +121,7 @@ class ChildManagementContextInitializer implements BeanRegistrationAotProcessor, @Override public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { Assert.isInstanceOf(ConfigurableApplicationContext.class, this.parentContext); - BeanFactory parentBeanFactory = ((ConfigurableApplicationContext) this.parentContext).getBeanFactory(); + BeanFactory parentBeanFactory = this.parentContext.getBeanFactory(); if (registeredBean.getBeanClass().equals(getClass()) && registeredBean.getBeanFactory().equals(parentBeanFactory)) { ConfigurableApplicationContext managementContext = createManagementContext(); @@ -136,6 +136,10 @@ class ChildManagementContextInitializer implements BeanRegistrationAotProcessor, return false; } + ConfigurableApplicationContext getManagementContext() { + return this.managementContext; + } + private void registerBeans(ConfigurableApplicationContext managementContext) { if (this.applicationContextInitializer != null) { this.applicationContextInitializer.initialize(managementContext); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java index 949261ff4c6..45106db8e1a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfigurationTests.java @@ -16,21 +16,30 @@ package org.springframework.boot.actuate.autoconfigure.web.server; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.SpringApplication; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; +import org.springframework.boot.origin.OriginLookup; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -95,6 +104,25 @@ class ManagementContextAutoConfigurationTests { .hasMessageStartingWith("Management-specific server address cannot be configured")); } + @Test // gh-45858 + void childEnvironmentShouldInheritPrefix() { + SpringApplication application = new SpringApplication(ChildEnvironmentConfiguration.class); + Map properties = new LinkedHashMap<>(); + properties.put("server.port", "0"); + properties.put("management.server.port", "0"); + application.setDefaultProperties(properties); + application.setEnvironmentPrefix("my"); + try (ConfigurableApplicationContext parentContext = application.run()) { + ChildManagementContextInitializer initializer = parentContext + .getBean(ChildManagementContextInitializer.class); + ConfigurableApplicationContext managementContext = initializer.getManagementContext(); + PropertySource systemEnvironmentPropertySource = managementContext.getEnvironment() + .getPropertySources() + .get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME); + assertThat(((OriginLookup) systemEnvironmentPropertySource).getPrefix()).isEqualTo("my"); + } + } + private Consumer numberOfOccurrences(String substring, int expectedCount) { return (charSequence) -> { int count = StringUtils.countOccurrencesOf(charSequence.toString(), substring); @@ -102,4 +130,12 @@ class ManagementContextAutoConfigurationTests { }; } + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ ManagementContextAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class, + ServletManagementContextAutoConfiguration.class, WebEndpointAutoConfiguration.class, + EndpointAutoConfiguration.class, DispatcherServletAutoConfiguration.class }) + static class ChildEnvironmentConfiguration { + + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java index ee440345c52..81d136025ae 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java @@ -36,6 +36,7 @@ import org.springframework.util.StringUtils; * {@link SystemEnvironmentOrigin} for every system environment property. * * @author Madhura Bhave + * @author Phillip Webb * @since 2.0.0 */ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { @@ -49,10 +50,14 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + postProcessEnvironment(environment, application.getEnvironmentPrefix()); + } + + private void postProcessEnvironment(ConfigurableEnvironment environment, String environmentPrefix) { String sourceName = StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME; PropertySource propertySource = environment.getPropertySources().get(sourceName); if (propertySource != null) { - replacePropertySource(environment, sourceName, propertySource, application.getEnvironmentPrefix()); + replacePropertySource(environment, sourceName, propertySource, environmentPrefix); } } @@ -74,6 +79,23 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements this.order = order; } + /** + * Post-process the given {@link ConfigurableEnvironment} by copying appropriate + * settings from a parent {@link ConfigurableEnvironment}. + * @param environment the environment to post-process + * @param parentEnvironment the parent environment + * @since 3.4.12 + */ + public static void postProcessEnvironment(ConfigurableEnvironment environment, + ConfigurableEnvironment parentEnvironment) { + PropertySource parentSystemEnvironmentPropertySource = parentEnvironment.getPropertySources() + .get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME); + if (parentSystemEnvironmentPropertySource instanceof OriginAwareSystemEnvironmentPropertySource parentOriginAwareSystemEnvironmentPropertySource) { + new SystemEnvironmentPropertySourceEnvironmentPostProcessor().postProcessEnvironment(environment, + parentOriginAwareSystemEnvironmentPropertySource.getPrefix()); + } + } + /** * {@link SystemEnvironmentPropertySource} that also tracks {@link Origin}. */ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java index 6779815197b..8c92e9e7bcc 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessorTests.java @@ -105,4 +105,18 @@ class SystemEnvironmentPropertySourceEnvironmentPostProcessorTests { assertThat(replaced.getPrefix()).isEqualTo("my"); } + @Test + void postProcessWithParentEnvironmentShouldApplyPrefix() { + SpringApplication application = new SpringApplication(); + application.setEnvironmentPrefix("my"); + new SystemEnvironmentPropertySourceEnvironmentPostProcessor().postProcessEnvironment(this.environment, + application); + StandardEnvironment child = new StandardEnvironment(); + SystemEnvironmentPropertySourceEnvironmentPostProcessor.postProcessEnvironment(child, this.environment); + OriginAwareSystemEnvironmentPropertySource replaced = (OriginAwareSystemEnvironmentPropertySource) child + .getPropertySources() + .get("systemEnvironment"); + assertThat(replaced.getPrefix()).isEqualTo("my"); + } + }