加载特定于环境的属性以与PropertyPlaceholderConfigurer一起使用?

时间:2010-02-05 03:47:55

标签: java spring properties

这似乎是一个非常常见的问题,但我没有就最佳方法找到任何形式的共识,所以我在这里提出这个问题。

我正在使用Spring Batch和Spring开发一个命令行Java应用程序。我正在使用属性文件和PropertyPlaceholderConfigurer,但我不太确定处理多个环境(dev,test等)的属性文件的最佳方法。我的谷歌搜索只是以编程方式加载属性(即在Java代码本身中),这对我正在做的事情不起作用。

我考虑过的一种方法是简单地将每个环境的属性文件放在服务器上,并通过命令行参数将文件的目录添加到类路径中,但是我一直无法使用该方法加载文件。

我正在考虑的另一种方法是在jar中包含所有属性文件,并使用系统属性或命令行参数在运行时填写属性文件的名称,如下所示:

<bean id="propertyConfigurer"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath:job.properties.${env}</value>
        </list>
    </property>
</bean>

我倾向于后一种解决方案,但我也想看看是否有一种更好的方法我会忽略。

我还应该提到我必须在运行时而不是在构建中进行替换。我被限制使用的过程需要一个单独的构建,它将通过环境升级到生产,所以我无法使用替换ala Maven或Ant。

8 个答案:

答案 0 :(得分:11)

基本上,您有一个已完成的JAR,您希望将其放入另一个环境中,如果没有任何修改,它会在运行时选择适当的属性。如果这是正确的,那么以下方法是有效的:

1)依靠用户主目录中存在属性文件。

配置PropertyPlaceholderConfigurer以引用JAR外部的属性文件,如下所示:

<bean id="applicationProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreUnresolvablePlaceholders" value="false"/>
    <property name="order" value="1"/>
    <property name="locations">
      <list>
        <!-- User home holds secured information -->
        <value>file:${user.home}/MyApp/application.properties</value>
      </list>
    </property>
  </bean>

操作系统将保护application.properties文件的内容,以便只有合适的人才能访问它。由于首次运行应用程序时此文件不存在,因此请创建一个简单的脚本,在启动时询问用户是否有关键值(例如用户名,密码,Hibernate方言等)。为命令行界面提供广泛的帮助和合理的默认值。

2)如果您的应用程序位于受控环境中以便可以看到数据库,那么问题可以简化为使用上面的技术1)创建基本凭据以在上下文启动期间连接到数据库然后执行替换使用通过JDBC读取的值。您将需要一个两阶段的应用程序启动方法:阶段1调用父上下文,其中application.properties文件填充JdbcTemplate和关联的DataSource;第2阶段调用引用父级的主上下文,以便可以在JdbcPropertyPlaceholderConfigurer中配置JdbcTemplate。

这种代码的一个例子是:

public class JdbcPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {

  private Logger log = Logger.getLogger(JdbcPropertyPlaceholderConfigurer.class);
  private JdbcTemplate jdbcTemplate;
  private String nameColumn;
  private String valueColumn;
  private String propertiesTable;

  /**
   * Provide a different prefix
   */
  public JdbcPropertyPlaceholderConfigurer() {
    super();
    setPlaceholderPrefix("#{");
  }

  @Override
  protected void loadProperties(final Properties props) throws IOException {
    if (null == props) {
      throw new IOException("No properties passed by Spring framework - cannot proceed");
    }
    String sql = String.format("select %s, %s from %s", nameColumn, valueColumn, propertiesTable);
    log.info("Reading configuration properties from database");
    try {
      jdbcTemplate.query(sql, new RowCallbackHandler() {

        public void processRow(ResultSet rs) throws SQLException {
          String name = rs.getString(nameColumn);
          String value = rs.getString(valueColumn);
          if (null == name || null == value) {
            throw new SQLException("Configuration database contains empty data. Name='" + name + "' Value='" + value + "'");
          }
          props.setProperty(name, value);
        }

      });
    } catch (Exception e) {
      log.fatal("There is an error in either 'application.properties' or the configuration database.");
      throw new IOException(e);
    }
    if (props.size() == 0) {
      log.fatal("The configuration database could not be reached or does not contain any properties in '" + propertiesTable + "'");
    }
  }

  public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
  }

  public void setNameColumn(String nameColumn) {
    this.nameColumn = nameColumn;
  }

  public void setValueColumn(String valueColumn) {
    this.valueColumn = valueColumn;
  }

  public void setPropertiesTable(String propertiesTable) {
    this.propertiesTable = propertiesTable;
  }

}

然后在Spring中配置如上所示(请注意,order属性位于通常的$ prefixed占位符之后):

  <!-- Enable configuration through the JDBC configuration with fall-through to framework.properties -->
  <bean id="jdbcProperties" class="org.example.JdbcPropertyPlaceholderConfigurer">
    <property name="ignoreUnresolvablePlaceholders" value="false"/>
    <property name="order" value="2"/>
    <property name="nameColumn" value="name"/>
    <property name="valueColumn" value="value"/>
    <property name="propertiesTable" value="my_properties_table"/>
    <property name="jdbcTemplate" ref="configurationJdbcTemplate"/> <!-- Supplied in a parent context -->
  </bean>

这将允许在Spring配置中进行以下操作

<!-- Read from application.properties -->
<property name="username">${username}</property>  
...
<!-- Read in from JDBC as part of second pass after all $'s have been fulfilled -->
<property name="central-thing">#{name.key.in.db}</property> 

3)当然,如果您在Web应用程序容器中,那么您只需使用JNDI。但你不是这样,你不能。

希望这有帮助!

答案 1 :(得分:8)

您可以使用<context:property-placeholder location="classpath:${target_env}configuration.properties" /> 在Spring XML中,使用命令行参数(${target_env})配置-Dtarget_env=test.

从Spring 3.1开始,您可以使用<context:property-placeholder location="classpath:${target_env:prod.}configuration.properties" />并指定默认值,从而无需在命令行上设置值。

如果Maven是一个选项,可以在插件执行期间设置Spring变量,例如在测试或集成测试执行期间。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.12</version>
    <configuration>
        <systemPropertyVariables>
            <target_env>test.</target_env>
        </systemPropertyVariables>
    </configuration>
</plugin>

我假设不同的Maven配置文件也可以使用。

答案 2 :(得分:7)

Spring Property Placeholder Configurer - 一些不太明显的选项

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="classpath:db.properties"></property>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="${db.url.${mode}}" />
    <property name="username" value="${db.username.${mode}}" />
    <property name="password" value="${db.password.${mode}}" />
</bean>

${db.username.${mode}}:此处&#34;模式&#34;定义项目模式(环境) - dev / prod 属性文件如下所示:

#Database properties
#mode dev/prod
mode=dev

#dev db properties
db.url.dev=jdbc:mysql://localhost:3306/dbname
db.username.dev=root
db.password.dev=root

#prod db properties
db.url.prod=jdbc:mysql://localhost:3306/dbname
db.username.prod=root
db.password.prod=root

答案 3 :(得分:3)

我过去通常采用的方式是在打包/部署时以某种方式替换环境(dev / test / prod)。

可以将正确的配置文件复制到服务器上的正确位置,也可以只在部署包中捆绑正确的配置文件。如果你使用Ant / Maven,这应该是相当简单的。你使用哪种构建工具? Ant / Maven,它应该为您提供替换值的能力。

使用PropertyPlaceholderConfigurer的另一种替代方法是SYSTEM_PROPERTIES_MODE_OVERRIDE属性。您可以使用它来设置要通过系统属性加载的属性文件的位置,请参阅:

http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.html#SYSTEM_PROPERTIES_MODE_OVERRIDE

希望有所帮助。

答案 4 :(得分:3)

我同意 - 它不应该是构建时配置,因为您希望将完全相同的有效负载部署到各种上下文。

PropertyPlaceHolderConfigurer的Locations属性可以使用各种类型的资源。也可以是文件系统资源或网址?因此,您可以将配置文件的位置设置为本地服务器上的文件,然后无论何时运行,它都将以该服务器上的配置文件指定的模式运行。如果您有特定服务器运行的特定服务器,这将工作正常。

虽然看起来你想在同一台服务器上以不同的模式运行相同的应用程序,但在行之间进行读取。在这种情况下,我建议通过命令行参数传递配置文件的位置。将此值传递给PropertyPlaceHolderConfigurer会有点棘手,但并非不可能。

答案 5 :(得分:1)

对于构建时替换,我使用Maven构建属性进行变量替换。您可以确定要在Maven settings.xml文件中加载哪些属性,该文件可能特定于环境。对于使用PPC的生产属性,请参阅此blog

答案 6 :(得分:1)

您好,阅读Spring in Action后发现了Spring提供的解决方案。 配置文件或条件:您可以创建多个配置文件,例如。测试,开发,生产等。

在确定哪些配置文件处于活动状态时,Spring会尊重两个单独的属性: spring.profiles.active和spring.profiles.default。如果是spring.profiles.active 如果设置,则其值确定哪些配置文件处于活动状态。但如果春天 .profiles.active未设置,然后Spring查找spring.profiles.default。如果没有 spring.profiles.active也没有设置spring.profiles.default,那就没有了 活动配置文件,仅创建未定义为在配置文件中的那些bean。

有几种方法可以设置这些属性: 1作为DispatcherServlet上的初始化参数 2作为Web应用程序的上下文参数 3作为JNDI条目 4作为环境变量 5作为JVM系统属性 6在集成测试类上使用@ActiveProfiles批注

答案 7 :(得分:0)

我使用classpath选项并调整Jetty中每个环境的类路径。在jetty-maven-plugin中,您可以为测试类设置一个目录,并将测试资源放在那里。

对于非本地环境(测试/生产),我使用环境标志并将相应的文件发送到$ JETTY_HOME / resources文件夹(内置于Jetty的类路径中)