Reputation: 53
There's a behavior for which I can't find the related documentation. Let's assume the following code. It is supposed to display in the console what has been configured with the foo.bar property :
@SpringBootApplication
@Component
public class Test {
@Autowired
TestConfig testConfig;
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext run = new SpringApplication(Test.class).run(args);
Test test = run.getBean(Test.class);
test.run();
}
public void run() throws Exception {
testConfig.getBar().entrySet().forEach(e -> {
System.out.println(e.getKey() + " " + e.getValue());
});
}
@Configuration
@ConfigurationProperties(ignoreUnknownFields = false, prefix = "foo")
static class TestConfig {
private Map<SomeEnum, String> bar = new HashMap<>();
public Map<SomeEnum, String> getBar() {
return bar;
}
public void setBar(Map<SomeEnum, String> bar) {
this.bar = bar;
}
}
}
If you set the following property in application.yml (foo.bar[A_VALUE]: from application.yml
), it will be correctly picked up and display "from application.yml" in the console, nothing fancy
Now if you use the exact same code, but this time you want to override the property defined in application.yml with a command line argument and set --foo.bar[aValue]="from command line"
as a command line arg (note that this time I used camel case for the enum reference). It is still displayed "from application.yml" in the console and not the overridden property.
If I chose Uppercase enum in command line and camel case enum in the application.yml, it will still display the same thing to the console.
Is it the expected behavior ? What is the rule in such situation ?
From what I've tested, it is the exact opposite of what is described in https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config
I've tested with spring boot 1.2.5.RELEASE and with 1.3.0.RELEASE
Thanks for your time
Upvotes: 3
Views: 25134
Reputation: 3830
In config properties, the last one wins:
application.yaml (1)
foo.bar.A_VALUE : 111
foo.bar.aValue : 222 # output '222', overridden
application.yaml (2)
foo.bar.aValue : 222
foo.bar.A_VALUE : 111 # output '111', overridden
So, when debug at PropertiesConfigurationFactory#doBindPropertiesToTarget() (I'm using spring-boot 1.5.2), with :
JVM options -Dfoo.bar.B_VALUE=b11 -Dfoo.bar.cValue=c11
application.yml :
foo.bar:
A_VALUE: aaa
B_VALUE: bbb
C_VALUE: ccc
D_VALUE: dddd
dValue: ddd
logging.level:
org.springframework.boot.env: TRACE
org.springframework.boot.context.config: DEBUG
propertyValues#propertyValues which is LinkedHashMap, with the following property key order :
// keys are unique, when the same key, systemProperties take first.
0. `foo.bar.B_VALUE` from 'systemProperties'
1. `foo.bar.cValue` from 'systemProperties'
2. `foo.bar.A_VALUE` from 'applicationConfig: [classpath:/application.yml]'
3. `foo.bar.C_VALUE` from 'applicationConfig: [classpath:/application.yml]'
4. `foo.bar.D_VALUE` from 'applicationConfig: [classpath:/application.yml]'
5. `foo.bar.dValue` from 'applicationConfig: [classpath:/application.yml]'
The console output is :
B_VALUE b11 // systemProperties first
A_VALUE aaa
D_VALUE ddd // the last one wins. (foo.bar.dValue)
C_VALUE ccc //the last one wins. (foo.bar.C_VALUE)
In my test, with JSON notation :
PropertiesConfigurationFactory#propertySources = {
class : ConfigurationPropertiesBindingPostProcessor$FlatPropertySources
propertySources : [ {
class : PropertySourcesPlaceholderConfigurer$1
name : 'environmentProperties',
source: {
class : StandardServletEnvironment,
propertySource : {
class : MutablePropertySources,
propertySourceList : [{
class: PropertySource$StubPropertySource,
name : 'servletConfigInitParams'
}, {
class: MapPropertySource,
name : 'systemProperties'
}, {
class: SystemEnvironmentPropertySource,
name : 'systemEnvironment'
}, {
class: RandomValuePropertySource,
name : 'random'
}, {
class: MapPropertySource,
name : 'applicationConfig: [classpath:/application.yml]'
}, {
class: MapPropertySource,
name : 'refresh'
}]
}
}
}, {
class : PropertiesPropertySource,
name : 'localProperties',
source: <Properties> // empty in my test
}]
}
NOTICE: Class PropertiesConfigurationFactory
has been removed in spring boot 2.x.
PS: When searching for this question, I wanted to figure out what notions Enum values (such A_VALUE) can be written in config properties. The answer is just as @Mohit said.
RelaxedDataBinder#bind()
RelaxedConversionService#convert()
1. try DefaultConvertionService#convert()
# only support `A_VALUE`
StringToEnumConverterFactory#StringToEnum#convert()
2. then GenericConversionService#convert()
# the config key can be :
# 0 = "a-value"
# 1 = "a_value"
# 2 = "aValue"
# 3 = "avalue"
# 4 = "A-VALUE"
# 5 = "A_VALUE"
# 6 = "AVALUE"
RelaxedConversionService$StringToEnumIgnoringCaseConverterFactory$StringToEnum#convert()
Upvotes: 2
Reputation: 1755
Spring uses StringToEnum
for converting string values to enum. This class internally uses java.lang.Enum#valueOf
method to do the conversion. Enum class creates a map and then performs lookup on this map. Hence, the key must match the exact case for lookup to succeed.
Below test case will validate that:
enum SomeEnum{
A, B
}
public class EnumTest {
public static void main(String[] args) {
SomeEnum e1 = Enum.valueOf(SomeEnum.class, "A");
System.out.println(e1);
SomeEnum e2 = Enum.valueOf(SomeEnum.class, "a"); //throws exception
}
}
Hence, spring fall backs to value defined in your application.yml when it fails to convert value passed from command line.
EDIT
If you try the following combinations:
foo.bar[A_VALUE]: from application.yml
foo.bar[A_VALUE]: from command line
{A_VALUE=from command line}
foo.bar[A_VALUE]: from application.yml
foo.bar[aValue]: from command line
{A_VALUE=from application.yml}
foo.bar[aValue]: from application.yml
foo.bar[A_VALUE]: from command line
{A_VALUE=from application.yml}
foo.bar[aValue]: from application.yml
foo.bar[aValue]: from command line
{A_VALUE=from command line}
1st & 4th Scenario - As key names are exactly same, first command line property is set. This property is added to processed list and thus YML property is ignored.
2nd & 3rd Scenario - As key names are different, both command line & YML properties are processed. YML being processed second override the value set from command line.
Upvotes: 2