【问题标题】:How do I configure Spring to partially and optionally override properties?如何将 Spring 配置为部分和可选地覆盖属性?
【发布时间】:2013-12-11 07:54:14
【问题描述】:

我想要一个可以在某些环境中覆盖特定属性的属性设置。例如,我们默认的 dev JDBC 属性是:

  • db.driverClassName=com.mysql.jdbc.Driver
  • db.url=jdbc:mysql://localhost:3306/ourdb
  • db.username=root
  • db.password=

问题是我们的一些开发人员希望在数据库上使用不同的用户名/密码,甚至可能是非本地托管的数据库。我们的 rabbitMQ 配置也是如此,它目前使用类似的 localhost、guest/guest 设置。能够在每个开发人员的基础上覆盖此配置的某些元素的属性将使我们能够将用于构建软件的大部分基础架构/安装要求从本地机器转移到专用服务器上。

我已经建立了一个简单的项目来围绕实现我想要的配置所需的配置,这是我第一次涉足 spring 属性配置的世界,因为到目前为止,已经完成了属性加载和管理带有一些自定义代码。这是我的设置:

class Main_PropertyTest {
    public static void main(String[] args) {
        String environment = System.getenv("APPLICATION_ENVIRONMENT"); // Environment, for example: "dev"
        String subEnvironment = System.getenv("APPLICATION_SUB_ENVIRONMENT"); // Developer name, for example: "joe.bloggs"
        System.setProperty("spring.profiles.active", environment);
        System.setProperty("spring.profiles.sub", subEnvironment);

        try(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertyTestConfiguration.class)) {
            Main_PropertyTest main = context.getBean(Main_PropertyTest.class);
            main.printProperty();
        }
    }

    private final String property;

    public Main_PropertyTest(String property) {
        this.property = property;
    }

    public void printProperty() {
        System.out.println("And the property is: '" + property + "'.");
    }
}

还有我的配置:

@Configuration
public class PropertyTestConfiguration {
    @Bean
    public static PropertySourcesPlaceholderConfigurer primaryPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource(System.getProperty("spring.profiles.active") + ".main.properties"));
        return propertySourcesPlaceholderConfigurer;
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer secondaryPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource(System.getProperty("spring.profiles.sub") + ".main.properties"));
        propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
        propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
        propertySourcesPlaceholderConfigurer.setOrder(-1);
        return propertySourcesPlaceholderConfigurer;
    }

    @Bean
    public Main_PropertyTest main_PropertyTest(@Value("${main.property}") String property) {
        Main_PropertyTest main_PropertyTest = new Main_PropertyTest(property);
        return main_PropertyTest;
    }
}

为了完整起见,我的 dev.main.properties 和 test.main.properties:

main.property=dev

main.property=test

主要问题是我得到一个非法参数异常。据我所知,我写的应该是这个方法的 javaconfig 等效项:http://taidevcouk.wordpress.com/2013/07/04/overriding-a-packaged-spring-application-properties-file-via-an-external-file/ 不幸的是,我收到以下错误:java.lang.IllegalArgumentException:无法解析字符串值“${main.property}”中的占位符“main.property”。请注意,我还需要注意没有子环境的情况,这就是我开始的情况(尽管即使两个文件都存在我也会遇到相同的错误)。如果我删除了设置第二个 propertysourcesplaceholderconfigurer 的 bean,那么一切正常(我的意思是加载 dev.main.properties 并打印出“And the property is: 'dev'.”)。

第二个问题是代码看起来不太好,系统的每一层都需要设置两个 PSPC,以便它们可以访问这些属性。此外,它需要对 System.getProperty() 进行大量手动调用,因为我无法将 ${spring.profiles.active} 传递给 PSPC.setLocation();

注意:我已经尝试过@PropertySources({primaryproperties, secondaryProperties}),但是因为secondaryProperties 不存在所以失败了。我也尝试过@Autowired Environment 环境;并从中获取属性,但辅助 PSPC 会导致环境无法自动连接...

所以按照这个冗长的解释,我的问题是:

  • 这是解决此问题的正确方法吗?
  • 如果是这样,我的配置有什么问题?
  • 如何简化配置(如果有的话)?
  • 是否有替代机制可以解决我的问题?

感谢您的宝贵时间! :)

【问题讨论】:

  • 对于初学者,您的 PropertySourcesPlaceholderConfigurer 带注释的 bean 方法应该是静态方法。接下来为什么使用 2 PropertySourcesPlaceholderConfigurer 而你可以简单地使用 1?接下来,我强​​烈建议您简单地使用 @PropertySource 注释来加载您的文件。
  • 您好,感谢您的回复。出于兴趣,为什么它们应该是静态的?既然你提到它,我注意到我一直在使用的所有示例都是静态的......我想我需要使用 2 个 PSPC,因为其中一个源可能不存在,如果我只有一个,那么它会告诉我文件启动时不存在,但是我可能误解了 PSPC 的能力,或者缺少一些可以实现这一点的功能?我确实尝试过@PropertySource,但遇到了与单个 PSPC 相同的问题。如果文件不存在,那么我会收到一个错误,似乎没有办法忽略丢失文件的失败。
  • 它们需要是静态的,因为它们需要先于其他任何东西可用,这是一种生命周期的东西。您仍然可以将 @PropertySource 用于初始文件(从始终存在的配置来看)。然后注入 Environment 并自己添加一个 PropertySource(如果文件存在)。
  • 有道理,谢谢。我将尝试注入环境并添加属性源,这听起来像是解决方案。 :)
  • 我修改了答案以反映这一变化。

标签: java spring properties spring-java-config


【解决方案1】:

在使用 java config 配置 BeanFactoryPostProcessor 时,您的配置存在缺陷,方法应该是静态的。然而,它可以更容易,而不是注册您自己的PropertySourcesPlaceholderConfigurer,而是使用默认的@PropertySource 支持。

将您的 jav 配置重写为以下内容

@Configuration
@PropertySource(name="main", value= "${spring.profiles.active}.main.properties")
public class PropertyTestConfiguration {

    @Autowired
    private Environment env;

    @PostConstruct
    public void initialize() {
        String resource = env.getProperty("spring.profiles.sub") +".main.properties";
        Resource props = new ClassPathResource(resource);
        if (env instanceof ConfigurableEnvironment && props.exists()) {
            MutablePropertySources sources = ((ConfigurableEnvironment) env).getPropertySources();
            sources.addBefore("main", new ResourcePropertySource(props)); 
        }
    }

    @Bean
    public Main_PropertyTest main_PropertyTest(@Value("${main.property}") String property) {
        Main_PropertyTest main_PropertyTest = new Main_PropertyTest(property);
        return main_PropertyTest;
    }
}

这应该首先加载dev.main.properties 和另外的test.main.properties,这将覆盖之前加载的属性(当然填充时)。

【讨论】:

  • 谢谢,我已经更新了使用静态 bean 的问题,但这并没有解决我的问题。刚看到你的评论,所以我会试试。 :)
  • 我会更新我的答案以反映之前的评论。您还可以通过首先在Environment 上调用containsProperty 来检查spring.profiles.sub 属性是否已设置。这样一来,设置该属性就完全是可选的了。
  • 完美,非常感谢!这更整洁了,我现在很高兴。 :)
  • 通过使用环境,您也可以简单地使用环境变量,而不是将它们设置为其他系统变量。尽管由于您正在设置 spring.profiles.active 变量这一事实,您可能需要它。附加注释而不是在 @PostConstruct 方法中执行,您可以将其移动到 ApplicationContextInitializer 代替。 (在使用 Web 应用程序时特别有用)。
  • 谢谢你的提示,我去看看ApplicationContextInitializer
【解决方案2】:

我在覆盖集成测试中已经存在的属性时遇到了类似的问题

我想出了这个解决方案:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {
    SomeProdConfig.class,
    MyWebTest.TestConfig.class
})
@WebIntegrationTest
public class MyWebTest {


    @Configuration
    public static class TestConfig {

        @Inject
        private Environment env;


        @PostConstruct
        public void overwriteProperties() throws Exception {
            final Map<String,Object> systemProperties = ((ConfigurableEnvironment) env)
                .getSystemProperties();
            systemProperties.put("some.prop", "test.value");
        }
    }

【讨论】:

    猜你喜欢
    • 2018-12-31
    • 2018-09-05
    • 1970-01-01
    • 2021-06-22
    • 1970-01-01
    • 2012-08-02
    • 1970-01-01
    • 2010-12-17
    • 2021-09-14
    相关资源
    最近更新 更多