Application has a ben annotated with@ConfigurationProperties("test") that has a List<String> attribute called users. at the beginning, one item listed in the external configuration file
test:
users:
- Andy
as the application is running, change the configuration file to the following
test:
users:
or
test:
users: null
what I expect is users=null or users =[], while the result is still users[0]=Andy
it‘s ok for the following pattern :
test:
users: ""
Here's a test that fails with Spring Boot 2.1.17.RELEASE
@SpringBootTest(classes=ConfigPropertiesTest.class)
@RunWith(SpringRunner.class)
@EnableConfigurationProperties(ConfigPropertiesTest.TestProperties.class)
@TestPropertySource(properties = {"test.users[0]=Andy"})
public class ConfigPropertiesTest {
@Autowired
private TestProperties properties;
@Autowired
private ConfigurationPropertiesBindingPostProcessor processor;
@Autowired
private ConfigurableEnvironment environment;
@Test
public void liveRebindToNull() throws Exception {
assertEquals(properties.getUsers().get(0),"Andy");
setNullProperties("test.users");
assertTrue(environment.getPropertySources().get("test").containsProperty("test.users"));;
assertNull(environment.getPropertySources().get("test").getProperty("test.users"));
processor.postProcessBeforeInitialization(properties, "testProperties");
assertEquals(properties.getUsers().size(), 0); //**_TEST NG_**
}
/**
* TestPropertyValues has no public method set null value, using reflect to do it
*
* @param prefix
* @throws Exception
*/
private void setNullProperties(String prefix) throws Exception {
TestPropertyValues testPropertyValues = TestPropertyValues.empty();
Method method = TestPropertyValues.class.getDeclaredMethod("and", Stream.class);
method.setAccessible(true);
TestPropertyValues.Pair pair = new TestPropertyValues.Pair(prefix,null);
testPropertyValues = (TestPropertyValues) method.invoke(testPropertyValues, Stream.of(pair));
testPropertyValues.applyTo(environment);
}
@ConfigurationProperties("test")
public static class TestProperties {
private List<String> users;
public List<String> getUsers() {
return users;
}
public void setUsers(List<String> users) {
this.users = users;
}
}
}
Comment From: wilkinsona
Spring Boot does not support the reloading of properties at runtime. You may be interested in Spring Cloud's support for changing the environment and its refresh scope.
Comment From: chengaofeng
I am sorry not make myself clear.My project is indeed a spring cloud application, The Properties bean is also annotated by @RefreshScope.While The config server is alibaba‘s nacos. When I change the external config file as above(set the value to null), the properties's value didn't change as expect.
In debug mode, when the external config file changed from
test:
users:
- Andy
to
test:
users:
I can find that the propertySource in Environment#propertySources#propertySourceList changed to test.users => null, and then an EnvironmentChangeEvent published. but after the ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization method invoked, the value of the users in the Properties bean is still users[0]=Andy
Then I dig into the source of
org.springframework.boot.context.properties.source.SpringConfigurationPropertySource#find(PropertyMapping mapping)
private ConfigurationProperty find(PropertyMapping mapping) {
String propertySourceName = mapping.getPropertySourceName();
Object value = getPropertySource().getProperty(propertySourceName);
if (value == null) {
return null;
}
...
}
when the value is null, the method is return null, which means no such item. while in my situation,set test.users=>null is different from not configuring at all
Spring Cloud version: Hoxton.SR8
Spring Boot version: 2.2.5.RELEASE
@wilkinsona,I'm appreciate that you could take a look at this problem again.
Comment From: philwebb
See https://github.com/spring-projects/spring-framework/issues/25142 for background on null values in a MapPropertySource and https://github.com/spring-projects/spring-boot/issues/21542#issuecomment-635107511 for a previous Boot issue.
Comment From: philwebb
This is a limitation of the way that null is handled both in Boot and Spring Framework. I'm afraid the fix for this will be quite involved and is unlikely to happen soon.
Comment From: wilkinsona
28139 describes another use case for being able to clear the value of a property somehow.
Comment From: wilkinsona
https://github.com/spring-projects/spring-boot/pull/31037 is another case of #28139.
Comment From: Sroca3
@philwebb do you know what all is needed to get this fixed? If https://github.com/spring-projects/spring-framework/issues/19986 is addressed, would it be easy for spring-boot to support nulls? Trying to see how to move this forward.
Comment From: wilkinsona
It's not just https://github.com/spring-projects/spring-framework/issues/19986. Spring Framework's Environment abstraction would also have to be able to distinguish between an absent property and a property with a null value. https://github.com/spring-projects/spring-framework/issues/25142 was opened to explore this but it was effectively rejected and turned into a documentation issue.