Spring的PropertyOverrideConfigurer类强加的约束问题

时间:2014-05-14 09:35:55

标签: spring properties-file

我在使用我的应用时遇到了配置和属性管理问题

基本上我有很多属性文件,每个文件都包含许多属性(键/值)。

另一方面,我的应用使用了许多Spring环境配置文件(“dev”,“test”等)。

大多数这些属性对于所有应用程序的配置文件都是相同的,但有些属性不同。

我们的想法是在一个地方定义所有属性让每个配置文件根据配置文件要求覆盖这些属性。这就是我遇到问题的地方......

我尝试将PropertySourcesPlaceholderConfigurer作为要覆盖的属性的基本源(不依赖于任何特定的配置文件),然后将一些PropertyOverrideConfigurer绑定到将覆盖的特定配置文件基础来源。

以下是我的配置:

@Configuration
public class PropertyConfigurerConfiguration {

    static class defaultConfiguration {
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
            propertySourcesPlaceholderConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/*.properties"));
            return propertySourcesPlaceholderConfigurer;
        }
    }

    @Profile(Profiles.DEV)
    static class devConfiguration {
        @Bean
        public static PropertyOverrideConfigurer propertyOverrideConfigurer() throws IOException {
            PropertyOverrideConfigurer propertyOverrideConfigurer = new PropertyOverrideConfigurer();
            propertyOverrideConfigurer.setIgnoreResourceNotFound(Boolean.TRUE);
            propertyOverrideConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/dev/*.properties"));
            return propertyOverrideConfigurer;
        }
    }

    @Profile(Profiles.TEST)
    static class testConfiguration {

        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
            propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
            propertySourcesPlaceholderConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/*.properties"));
            Properties localProperties = new Properties();
            localProperties.setProperty("google_api.key", "TEST-API-KEY");
            propertySourcesPlaceholderConfigurer.setProperties(localProperties);
            propertySourcesPlaceholderConfigurer.setLocalOverride(Boolean.TRUE);
            return propertySourcesPlaceholderConfigurer;
        }

        @Bean
        public static PropertyOverrideConfigurer propertyOverrideConfigurer() throws IOException {
            PropertyOverrideConfigurer propertyOverrideConfigurer = new PropertyOverrideConfigurer();
            propertyOverrideConfigurer.setIgnoreResourceNotFound(Boolean.TRUE);
            propertyOverrideConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:META-INF/props/test/*.properties"));
            return propertyOverrideConfigurer;
        }
    }
    ...

请注意我必须专门为测试配置文件重新定义PropertySourcesPlaceholderConfigurer并使用本地覆盖。这只是一个暂时的黑客,我想摆脱......

因此我遇到的问题如下:

  • 我不能将任意密钥考虑在内并覆盖基础PropertySourcesPlaceholderConfigurer中的密钥。它似乎必须遵循beanName.property模式,或者我必须使用setIgnoreInvalidKeys,但是不会考虑任意键。例如我不能拥有application.url=http://localhost:8080/myApp等属性并在@Component中使用它...

  • 更多问题还可能发生在jpa属性映射中:

请参阅以下配置:

entityManagerFactoryBean.setJpaPropertyMap(propertiesMap());

private Map<String, String> propertiesMap() {
        Map<String, String> propertiesMap = new HashMap<>();
        propertiesMap.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
        propertiesMap.put("hibernate.hbm2ddl.auto", "update");
        propertiesMap.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
        propertiesMap.put("hibernate.connection.charSet", "UTF-8");
        propertiesMap.put("hibernate.show_sql", ???);
        propertiesMap.put("hibernate.format_sql", ???);
        propertiesMap.put("hibernate.use_sql_comments", ???);
        return propertiesMap;
    }

覆盖配置器强加的约束似乎不允许覆盖hibernate.format_sql等属性......

我的问题是:如何使用PropertyOverrideConfigurer来满足我的应用要求(见上文)。或者是否有另一种解决方案来覆盖PropertySourcesPlaceholderConfigurer

编辑1

我修改了我的配置,我在上下文启动时遇到了新问题:

Caused by: java.io.FileNotFoundException: Could not open ServletContext resource [/classpath*:META-INF/props/dev/app-config.properties]
    at org.springframework.web.context.support.ServletContextResource.getInputStream(ServletContextResource.java:141)
    at org.springframework.core.io.support.EncodedResource.getInputStream(EncodedResource.java:143)
    at org.springframework.core.io.support.PropertiesLoaderUtils.fillProperties(PropertiesLoaderUtils.java:98)
    at org.springframework.core.io.support.PropertiesLoaderUtils.fillProperties(PropertiesLoaderUtils.java:72)
    at org.springframework.core.io.support.PropertiesLoaderUtils.loadProperties(PropertiesLoaderUtils.java:58)
    at org.springframework.core.io.support.ResourcePropertySource.<init>(ResourcePropertySource.java:64)
    at org.springframework.context.annotation.ConfigurationClassParser.processPropertySource(ConfigurationClassParser.java:323)
    at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:227)
    at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:205)
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:173)
    at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:241)
    at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:205)
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:182)
    at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:152)
    ... 18 more

修改后,这是我的课程:

@Configuration
@PropertySource(name = "default-configuration", value = { "classpath*:META-INF/props/app-config.properties", "classpath*:META-INF/props/database.properties",
        "classpath*:META-INF/props/email.properties" })
public class PropertyConfigurerConfiguration {
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(Boolean.TRUE);
        propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
        return propertySourcesPlaceholderConfigurer;
    }

    @Configuration
    @Profile(Profiles.DEV)
    @PropertySource(name = "dev-configuration", value = { "classpath*:META-INF/props/dev/app-config.properties", "classpath*:META-INF/props/dev/database.properties",
            "classpath*:META-INF/props/dev/email.properties" })
    public static class DevConfiguration {
    }

    @Configuration
    @Profile(Profiles.TEST)
    @PropertySource(name = "test-configuration", value = { "classpath*:META-INF/props/test/app-config.properties", "classpath*:META-INF/props/test/database.properties" })
    public static class TestConfiguration {
    }

}

编辑2 :我将所有classpath*:xx更改为classpath:xx,对编辑1中描述的问题进行了排序。但是我注意到dev属性源没有覆盖默认属性,即使用默认PropertySource中的键,而dev PropertySource中存在相同的键...

1 个答案:

答案 0 :(得分:1)

出于某种原因,它看起来很复杂。为什么不简单地为每个配置文件添加@PropertySource注释?包括默认值。

@Configuration

public class PropertyConfigurerConfiguration {

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Configuration
    @Profile(Profiles.DEV)
    @PropertySource(name="dev-configuration", value="classpath*:META-INF/props/dev/*.properties")
    public static class DevConfiguration{}

    @Configuration
    @Profile(Profiles.TEST)
    @PropertySource(name="test-configuration", value="classpath*:META-INF/props/test/*.properties")
    public static class DevConfiguration{}

    @Configuration
    @PropertySource(name="default-configuration", value="classpath*:META-INF/props/*.properties")
    public static class DefaultConfiguration() {}
}

这将始终加载默认值并根据配置文件添加其他配置文件,这应该覆盖默认配置中的属性。

另一种解决方案是创建ApplicationContextInitializer,在其PropertySource方法中添加initialize

public class MyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static String DEFAULT_CONFIG_LOCATION= "classpath*:META-INF/props/*.properties";

    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment env = applicationContext.getEnvironment();
        MutablePropertySources mps = env.getPropertySources();
        String[] profiles = env.getActiveProfiles();
        for (String profile : profiles) {
            String location = "classpath*:META-INF/props/"+profile+"/*.properties";
            Resource[] resources = applicationContext.getResources(location);
            if (resources != null) {
                for (Resource res: resources) {
                    mps.addLast(new ResourcePropertySource(res));
                }
            }
        }

        // Load defaults as last
        Resource[] resources = applicationContext.getResources(DEFAULT_CONFIG_LOCATION);
            if (resources != null) {
                for (Resource res: resources) {
                    mps.addLast(new ResourcePropertySource(res));
                }
            }
    }
}

然后,您可以在web.xml中将其注册为globalInitializerClasses,并将其应用于此应用程序中创建的所有ApplicationContext。这也意味着您可以删除@PropertySource注释和专用配置。这样做的好处是,如果您创建新的配置文件,则无需添加另一个@Configuration类来加载资源。

如果要替换hibernate属性,则必须首先使用Environment添加属性。

@Autowired
private Environment env;

private Map<String, String> propertiesMap() {
    Map<String, String> propertiesMap = new HashMap<>();
    propertiesMap.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
    propertiesMap.put("hibernate.hbm2ddl.auto", "update");
    propertiesMap.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");
    propertiesMap.put("hibernate.connection.charSet", "UTF-8");
    propertiesMap.put("hibernate.show_sql", env.getProperty("hibernate.show_sql", Boolean.class, false));
    propertiesMap.put("hibernate.format_sql", env.getProperty("hibernate.format_sql", Boolean.class, false));
    propertiesMap.put("hibernate.use_sql_comments", env.getProperty("hibernate.use_sql_comments", Boolean.class, false));
    return propertiesMap;
}

这将从Environment获取属性,从而从已配置的PropertySource获取属性,如果未找到,则在这种情况下将使用默认false