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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2204917/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 04:42:23  来源:igfitidea点击:

Load environment-specific properties for use with PropertyPlaceholderConfigurer?

javaspringproperties

提问by Ickster

This seems like a pretty common problem, but I haven't found any sort of consensus on the best method, so I'm posing the question here.

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

I'm working on a command-line Java application using Spring Batch and Spring. I'm using a properties file along with a PropertyPlaceholderConfigurer, but I'm a little unsure of the best way of handling the properties files for multiple environments (dev, test, etc.). My Googling is only turning up programmatic ways of loading the properties (i.e., in the Java code itself), which doesn't work for what I'm doing.

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

One approach I've considered is simply placing each environment's properties file on the server and adding the file's directory to the classpath via a command-line argument, but I've been having trouble loading the file using that method.

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

The other method I'm considering is to just include all the properties files in the jar and use a system property or command line argument to fill in the name of the properties file at runtime, like this:

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

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

I lean towards the latter solution, but I'm also looking to see if there's a better method I'm overlooking.

我倾向于后一种解决方案,但我也在寻找是否有更好的方法被忽略。

I should also mention that I have to make the substitution at runtime rather than in the build. The process I'm constrained to use requires a single build which will be promoted through the environments to production, so I'm unable to use substitution ala Maven or Ant.

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

采纳答案by Michael Wiles

I agree - it should not be a build time configuration as you want to deploy the exact same payload to the various contexts.

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

The Locations property of PropertyPlaceHolderConfigurer can take various types of resources. Can also be a filesystem resouce or a url? Thus you could set the location of the config file to a file on the local server and then whenever it runs it would run in the mode specified by the config file on that server. If you have particular servers for particular modes of running this would work fine.

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

Reading between the lines though it seems you want to run the same application in different modes on the same server. What I would suggest in this case is to pass the location of the config file via a command line parameter. It would be a little tricky to pass this value into the PropertyPlaceHolderConfigurer but would not be impossible.

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

回答by Jon

The way I've normally done this in the past is to perform a substitution of the environment (dev/test/prod) in some sort of way at package/deployment time.

我过去通常这样做的方法是在打包/部署时以某种方式执行环境(开发/测试/生产)的替换。

That can either copy the correct config file to the right location on the server or just bundle the correct config file in the deployment package. If you use Ant/Maven this should be fairly straightforward to achieve. Which build tool are you using? Ant/Maven, that should provide you with the ability to substitute a value.

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

Another alternative, which use PropertyPlaceholderConfigurer is that of the SYSTEM_PROPERTIES_MODE_OVERRIDE property. You can use this to set the location of the properties file you wish to load through a system property, see:

另一种使用 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

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

Hope that helps.

希望有帮助。

回答by harschware

For build time substitution I use Maven build properties for variable substitution. You can determine what properties to load in your Maven settings.xml file and the file could be specific to the environment. For production properties using PPC see this blog

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

回答by Gary Rowe

Essentially you have a finished JAR which you want to drop into another environment, and without any modification have it pick up the appropriate properties at runtime. If that is correct, then the following approaches are valid:

本质上,您有一个已完成的 JAR,您希望将其放入另一个环境中,并且无需任何修改即可在运行时获取适当的属性。如果这是正确的,那么以下方法是有效的:

1) Rely on the presence of a properties file in the user home directory.

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

Configure the PropertyPlaceholderConfigurer to reference a properties file external to the JAR like this:

配置 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>

The operating system will secure the contents of the application.properties file so that only the right people can have access to it. Since this file does not exist when you first run up the application, create a simple script that will interrogate the user for the critical values (e.g. username, password, Hibernate dialect etc) at start up. Provide extensive help and sensible default values for the command line interface.

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

2) If your application is in a controlled environment so that a database can be seen then the problem can be reduced to one of creating the basic credentials using technique 1) above to connect to the database during context startup and then performing substitution using values read via JDBC. You will need a 2-phase approach to application start up: phase 1 invokes a parent context with the application.properties file populating a JdbcTemplate and associated DataSource; phase 2 invokes the main context which references the parent so that the JdbcTemplate can be used as configured in the JdbcPropertyPlaceholderConfigurer.

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

An example of this kind of code would be this:

这种代码的一个例子是这样的:

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;
  }

}

The above would then be configured in Spring like this (note the order property comes after the usual $ prefixed placeholders):

然后将在 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>

This would allow the follow to occur in the Spring configuration

这将允许在 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) Of course, if you're in a web application container then you just use JNDI. But you're not so you can't.

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

Hope this helps!

希望这可以帮助!

回答by Mond Raymond

I use the classpath option and adjust the classpath per environment in Jetty. In the jetty-maven-plugin you can set a directory for testclasses and have your testresources located there.

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

For non-local environments (test / production) I use an environment flag and send the appropriate files to the $JETTY_HOME/resources folder (which is built into Jetty's classpath)

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

回答by enno

You could use <context:property-placeholder location="classpath:${target_env}configuration.properties" />in your Spring XML and configure ${target_env}using a command-line argument (-Dtarget_env=test.).

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

Starting in Spring 3.1 you could use <context:property-placeholder location="classpath:${target_env:prod.}configuration.properties" />and specify a default value, thereby eliminating the need to set the value on the command-line.

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

In case Maven IS an option, the Spring variable could be set during plugin execution, e.g. during test or integration test execution.

如果 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>

I assume different Maven profiles would also work.

我假设不同的 Maven 配置文件也可以工作。

回答by Navrattan Yadav

Spring Property Placeholder Configurer – A few not so obvious options

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}}: Here "mode" defines the project mode (environment) - dev / prod Properties file looks like:

${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

回答by Navrattan Yadav

Hi after reading Spring in Action found a solution provided by Spring. Profile Or Conditional : you can create multiple profile eg. test, dev, prod etc.

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

Spring honors two separate properties when determining which profiles are active: spring.profiles.active and spring.profiles.default . If spring.profiles.active is set, then its value determines which profiles are active. But if spring .profiles.active isn't set, then Spring looks to spring.profiles.default . If neither spring.profiles.active nor spring.profiles.default is set, then there are no active profiles, and only those beans that aren't defined as being in a profile are created.

在确定哪些配置文件处于活动状态时,Spring 遵循两个单独的属性: spring.profiles.active 和 spring.profiles.default 。如果 spring.profiles.active 被设置,那么它的值决定了哪些配置文件是活动的。但是如果 spring .profiles.active 没有设置,那么 Spring 会查找 spring.profiles.default 。如果 spring.profiles.active 和 spring.profiles.default 都没有设置,则没有活动配置文件,只有那些没有定义为配置文件的 bean 被创建。

There are several ways to set these properties: 1 As initialization parameters on DispatcherServlet 2 As context parameters of a web application 3 As JNDI entries 4 As environment variables 5 As JVM system properties 6 Using the @ActiveProfiles annotation on an integration test class

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