【问题标题】:Load environment-specific properties for use with PropertyPlaceholderConfigurer?加载特定于环境的属性以与 PropertyPlaceholderConfigurer 一起使用?
【发布时间】:2011-01-13 09:04:45
【问题描述】:

这似乎是一个很常见的问题,但我还没有就最佳方法达成任何共识,所以我在这里提出这个问题。

我正在使用 Spring Batch 和 Spring 开发命令行 Java 应用程序。我正在使用属性文件和 PropertyPlaceholderConfigurer,但我有点不确定处理多个环境(开发、测试等)的属性文件的最佳方式。我的谷歌搜索只是打开了加载属性的编程方式(即在 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。

【问题讨论】:

    标签: java spring properties


    【解决方案1】:

    Spring 属性占位符配置器——一些不太明显的选项

    <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}}:这里的“mode”定义了项目模式(环境)-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
    

    【讨论】:

    • 我们可以指定系统环境。模式值中属性文件中的变量。这样我们就不必对可部署代码进行任何更改。只需更改环境变量,我们就可以在任何地方部署。
    • RishiPandey,你会怎么做?告诉属性文件从系统环境变量设置模式值?
    【解决方案2】:

    你可以使用&lt;context:property-placeholder location="classpath:${target_env}configuration.properties" /&gt; 在您的 Spring XML 中并使用命令行参数 (-Dtarget_env=test.) 配置 ${target_env}

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

    如果 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 配置文件也可以工作。

    【讨论】:

      【解决方案3】:

      基本上你有一个完成的 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 属性位于通常的 $ 前缀占位符之后):

        <!-- 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。但你不是,所以你不能。

      希望这会有所帮助!

      【讨论】:

      • 我认为您关于提示初始属性集的建议很有趣。不幸的是,这不是我可以使用的东西,因为该应用程序将在所有环境中通过调度工具启动,并且我使用的命令字符串将无法从一次运行更改为下一次运行。
      • 请记住,只有初始安装需要创建 application.properties 文件。如果您可以安排系统管理员根据模板放置文件并根据需要进行配置,那么这可能会起作用。当然,这确实需要将应用程序部署在已知且受控的环境中。
      【解决方案4】:

      您好,在阅读 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 注解

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

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

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

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

          【讨论】:

            【解决方案7】:

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

            【讨论】:

            • 谢谢。请参阅我为 Jon 留下的评论;它也适用于您的建议。
            • 我想你可能没有读过我提到的博客。这是您将在运行时使用的方法。这样你就可以设置一个环境变量来获取你想要的任何属性文件,甚至是那些在你的 JAR 或 WAR 之外的文件。
            • 我对乔恩的评论的结尾说我也会检查他的其他建议;我对你的评论是为了暗示我也会检查你的其他建议。我真的不是很清楚。我很快就会阅读您提供的链接。谢谢!
            【解决方案8】:

            我过去通常这样做的方式是在打包/部署时以某种方式执行环境替换 (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

            希望对您有所帮助。

            【讨论】:

            • 感谢构建时的建议,但它不适用于我的情况。我已经更新了我的问题以反映这一点;我原本打算包含该信息并忘记了。我会检查你的其他建议。
            • 我使用 Maven 作为构建工具。在 maven 中打包时如何捆绑正确的配置。
            猜你喜欢
            • 2011-06-28
            • 2019-08-24
            • 1970-01-01
            • 1970-01-01
            • 2019-07-03
            • 1970-01-01
            • 1970-01-01
            • 2012-06-06
            • 2016-06-01
            相关资源
            最近更新 更多