具有不同大小的枚举值的ConfigurationProperties

时间:2015-11-24 19:01:19

标签: java spring spring-boot

有一种行为无法找到相关文档。 我们假设以下代码。它应该在控制台中显示已使用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;
        }
    }
}

如果你在application.yml(foo.bar[A_VALUE]: from application.yml)中设置了以下属性,它将被正确选中并在控制台中显示“from application.yml”,没什么特别的

现在,如果你使用完全相同的代码,但这次你要用命令行参数覆盖application.yml中定义的属性,并设置 - foo.bar[aValue]="from command line"作为命令行arg(注意这次我使用骆驼案例作为枚举参考)。它仍然在控制台中显示“来自application.yml”而不是被覆盖的属性。

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

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

根据我的测试,它与https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config

中描述的完全相反

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

感谢您的时间

2 个答案:

答案 0 :(得分:2)

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

  • application.yaml(1)

    foo.bar.A_VALUE : 111  
    foo.bar.aValue  : 222    # output '222', overrieded
    
  • application.yaml(2)

    foo.bar.aValue  : 222   
    foo.bar.A_VALUE : 111    # output '111', overrieded 
    

因此,在PropertiesConfigurationFactory#doBindPropertiesToTarget()进行调试时(我使用spring-boot 1.5.2),使用:

  • JVM otpions -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是LinkedHashMap,具有以下属性键顺序:

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

注意:类PropertiesConfigurationFactory已在spring boot 2.x中删除。

PS:在这个问题上搜索时,我想弄清楚可以在配置属性中写入哪些概念Enum值(例如A_VALUE)。答案正如@Mohit所说的那样。

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() 

答案 1 :(得分:0)

Spring使用StringToEnum将字符串值转换为枚举。此类内部使用java.lang.Enum#valueOf方法进行转换。枚举类创建一个地图,然后在此地图上执行查找。因此,密钥必须与查找成功的确切案例相匹配。

以下测试用例将验证:

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}

1st&amp;第4种情况 - 由于键名完全相同,因此设置了第一个命令行属性。此属性将添加到已处理的列表中,因此将忽略YML属性。

第二&amp;第三种情况 - 由于关键名称不同,命令行和处理YML属性。正在处理的YML将覆盖从命令行设置的值。