【问题标题】:Issue with constraints imposed by Spring's PropertyOverrideConfigurer classSpring 的 PropertyOverrideConfigurer 类施加的约束问题
【发布时间】:2014-05-14 09:35:54
【问题描述】:

我的应用遇到配置和属性管理问题

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

另一方面,我的应用使用了许多 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 并使用本地覆盖。这只是一个临时的 hack,我想摆脱它......

因此我遇到的问题如下:

  • 我不能在基础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 中存在相同的键...

【问题讨论】:

  • 在使用@PropertySource 注解时,spring 似乎不允许使用诸如*.propertiesclasspath*: 之类的通配符。
  • 关于 edit2,尝试添加另一个内部 @Configuration 类,并将其添加到带有 @Profile 注释的 @Configuration 类之后。 (见我修改后的答案)。
  • 我尝试了您修改后的答案(甚至将默认配置移到顶部,以便开发人员覆盖默认配置),但它仍然无法正常工作(它只选择默认配置)。这是我如何激活个人资料spring.profiles.active=dev...
  • 我会假设要维护顺序,但这可能在 jdk/平台组合上有所不同。我建议使用ApplicationContextInitializer 解决方案,更灵活,更强大,您可以保证文件的顺序。
  • 非常感谢马丁。那么我可能不得不使用上下文初始化程序......我很惊讶地看到一个属性源很难用相同的键覆盖另一个。 :-( 基本上这就是我想要实现的目标......

标签: spring properties-file


【解决方案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,在其initialize 方法中添加PropertySource

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,它将应用于在此应用程序中创建的所有 ApplicationContexts。这也意味着您可以删除 @PropertySource 注释和专用配置。这样做的好处是,如果您创建一个新的配置文件,您不必添加另一个 @Configuration 类来加载资源。

如果您想替换休眠属性,您必须首先使用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 获取属性,因此从配置的PropertySources 获取属性,如果没有找到,将在这种情况下使用默认的false

【讨论】:

  • 嗨貂。看起来非常整洁。有一点我不确定:@PropertySource 注释的 name 属性是做什么用的?我只在文档中找到了这个:指明这个属性源的名称。如果省略,将根据底层资源的描述生成一个名称。您能告诉我它的用途吗?
  • 用于注册属性源(可以省略)。但我发现它对调试/记录很有帮助,因为还会打印名称以显示在哪个 PropertySource 中找到了属性值。
  • 当我今晚回到我的开发机器时,我将尝试所有这些!再次感谢。
  • 嗨貂。我已经编辑了我的帖子。还有一点你忘了提到的是@PropertySource 注释不允许使用通配符。
  • 你是对的,我希望有支持。只是查看了来源,它不是。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-25
  • 1970-01-01
  • 2011-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-15
相关资源
最近更新 更多