spring-boot
spring-boot copied to clipboard
Support null values when binding properties
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;
}
}
}
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.
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.
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.
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.
#28139 describes another use case for being able to clear the value of a property somehow.
https://github.com/spring-projects/spring-boot/pull/31037 is another case of #28139.
@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.
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.