Java 在不同情况下使用枚举值的ConfigurationProperties

Java 在不同情况下使用枚举值的ConfigurationProperties,java,spring,spring-boot,Java,Spring,Spring Boot,有一种行为我找不到相关文档。 让我们假设以下代码。它应该在控制台中显示使用foo.bar属性配置的内容: @SpringBootApplication @Component public class Test { @Autowired TestConfig testConfig; public static void main(String[] args) throws Exception { ConfigurableApplicationContext

有一种行为我找不到相关文档。 让我们假设以下代码。它应该在控制台中显示使用foo.bar属性配置的内容:

@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;
        }
    }
}
@springboot应用程序
@组成部分
公开课考试{
@自动连线
TestConfig TestConfig;
公共静态void main(字符串[]args)引发异常{
ConfigurableApplicationContext run=新的SpringApplication(Test.class).run(args);
Test=run.getBean(Test.class);
test.run();
}
public void run()引发异常{
testConfig.getBar().entrySet().forEach(e->{
System.out.println(e.getKey()+“”+e.getValue());
});
}
@配置
@ConfigurationProperties(ignoreUnknownFields=false,前缀=“foo”)
静态类TestConfig{
私有映射栏=新的HashMap();
公共地图getBar(){
返回杆;
}
公共空白立根(地图栏){
这个.bar=bar;
}
}
}
如果您在application.yml(
foo.bar[A_值]:from application.yml
)中设置了以下属性,它将被正确拾取,并在控制台中显示“from application.yml”,而不显示任何内容

现在,如果您使用完全相同的代码,但这次您希望使用命令行参数覆盖application.yml中定义的属性,并将--
foo.bar[aValue]=“from command line”
设置为命令行参数(请注意,这次我对枚举引用使用了camel大小写)。它仍然显示在控制台中的“from application.yml”,而不是覆盖的属性

如果我在命令行中选择大写枚举,在application.yml中选择驼峰大小写枚举,它仍然会向控制台显示相同的内容

这是预期的行为吗? 在这种情况下,规则是什么

根据我的测试,它与中描述的完全相反

我已经用spring boot 1.2.5.RELEASE和1.3.0.RELEASE进行了测试


感谢您抽出时间

Spring使用
StringToEnum
将字符串值转换为enum。此类内部使用
java.lang.Enum#valueOf
方法进行转换。Enum类创建一个映射,然后在此映射上执行查找。因此,键必须与查找成功的确切情况匹配

以下测试用例将验证:

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
    }
}
因此,当spring无法转换从命令行传递的值时,它会返回到application.yml中定义的值


编辑

如果您尝试以下组合:

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}
第一个和第四个场景-由于键名称完全相同,因此设置了第一个命令行属性。此属性被添加到已处理列表中,因此YML属性被忽略


第二和第三种方案-由于键名称不同,因此会处理命令行和YML属性。第二个正在处理的YML将覆盖从命令行设置的值。

在配置属性中,最后一个将获胜:

  • application.yaml(1)

  • application.yaml(2)

因此,在调试时(我使用的是spring boot 1.5.2),使用:

  • JVM otpions
    -Dfoo.bar.B_VALUE=b11-Dfoo.bar.cvvalue=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
    
PropertyValue#是LinkedHashMap的PropertyValue,具有以下属性键顺序:

// keys are unique, when 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]'
控制台输出为:

B_VALUE b11  // systemProperties first 
A_VALUE aaa  
D_VALUE ddd  // the last one wins. (foo.bar.dValue) 
C_VALUE ccc  // ths last one wins. (foo.bar.C_VALUE)
在我的测试中,使用JSON符号:

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
    }]
}

恐怕这个答案不对。这两个属性的定义在Spring中都是正确的,分别是application.yml和application.yml。然后,应该覆盖第一个答案的答案将不被考虑。编辑原始答案以反映覆盖规则。
B_VALUE b11  // systemProperties first 
A_VALUE aaa  
D_VALUE ddd  // the last one wins. (foo.bar.dValue) 
C_VALUE ccc  // ths last one wins. (foo.bar.C_VALUE)
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
    }]
}
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()