From efdaae02e0ae8d2844d1fa78d35adc1175abbb6a Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sun, 11 May 2025 16:41:19 +0200 Subject: [PATCH] Add test for late binding from Environment property sources This new test serves as a "regression test" for behavior tested in Spring Boot. See gh-34861 --- ...ertySourcesPlaceholderConfigurerTests.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java index 24325f9552f..293ae6925c1 100644 --- a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java @@ -36,6 +36,7 @@ import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.MutablePropertySources; @@ -81,6 +82,43 @@ class PropertySourcesPlaceholderConfigurerTests { assertThat(ppc.getAppliedPropertySources()).isNotNull(); } + /** + * Ensure that a {@link PropertySource} added to the {@code Environment} after context + * refresh (i.e., after {@link PropertySourcesPlaceholderConfigurer#postProcessBeanFactory()} + * has been invoked) can still contribute properties in late-binding scenarios. + */ + @Test // gh-34861 + void replacementFromEnvironmentPropertiesWithLateBinding() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + MutablePropertySources propertySources = context.getEnvironment().getPropertySources(); + propertySources.addFirst(new MockPropertySource("early properties").withProperty("foo", "bar")); + + context.register(PropertySourcesPlaceholderConfigurer.class); + context.register(PrototypeBean.class); + context.refresh(); + + // Verify that placeholder resolution works for early binding. + PrototypeBean prototypeBean = context.getBean(PrototypeBean.class); + assertThat(prototypeBean.getName()).isEqualTo("bar"); + assertThat(prototypeBean.isJedi()).isFalse(); + + // Add new PropertySource after context refresh. + propertySources.addFirst(new MockPropertySource("late properties").withProperty("jedi", "true")); + + // Verify that placeholder resolution works for late binding: isJedi() switches to true. + prototypeBean = context.getBean(PrototypeBean.class); + assertThat(prototypeBean.getName()).isEqualTo("bar"); + assertThat(prototypeBean.isJedi()).isTrue(); + + // Add yet another PropertySource after context refresh. + propertySources.addFirst(new MockPropertySource("even later properties").withProperty("foo", "enigma")); + + // Verify that placeholder resolution works for even later binding: getName() switches to enigma. + prototypeBean = context.getBean(PrototypeBean.class); + assertThat(prototypeBean.getName()).isEqualTo("enigma"); + assertThat(prototypeBean.isJedi()).isTrue(); + } + @Test void localPropertiesViaResource() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -669,4 +707,23 @@ class PropertySourcesPlaceholderConfigurerTests { } } + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + static class PrototypeBean { + + @Value("${foo:bogus}") + private String name; + + @Value("${jedi:false}") + private boolean jedi; + + + public String getName() { + return this.name; + } + + public boolean isJedi() { + return this.jedi; + } + } + }