Load environment-specific properties for use with PropertyPlaceholderConfigurer?

后端 未结 8 1125
夕颜
夕颜 2020-12-23 17:24

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-l

相关标签:
8条回答
  • 2020-12-23 17:30

    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.).

    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.

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

    <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.

    0 讨论(0)
  • 2020-12-23 17:32

    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.

    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.

    0 讨论(0)
  • 2020-12-23 17:32

    Spring Property Placeholder Configurer – A few not so obvious options

    <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:

    #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
    
    0 讨论(0)
  • 2020-12-23 17:39

    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

    0 讨论(0)
  • 2020-12-23 17:43

    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 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.

    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

    0 讨论(0)
  • 2020-12-23 17:54

    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:

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

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

    <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.

    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.

    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):

      <!-- 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

    <!-- 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.

    Hope this helps!

    0 讨论(0)
提交回复
热议问题