Announcement Announcement Module
Collapse
No announcement yet.
NoXML Property Placeholder Configuration in Spring 3.1.0.RC1? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • NoXML Property Placeholder Configuration in Spring 3.1.0.RC1?

    I'm currently trying to convert a mostly-Java-with-some-XML Spring 3.0.6 configuration to a Java-only configuration with Spring 3.1.0.RC1.

    In my old XML context, I use a property placeholder
    Code:
      <context:property-placeholder location="file:${MY_CONFIG}"/>
    where MY_CONFIG is an environment variable pointing to a properties file.

    What is the equivalent of this in a Java configuration?

    Here is what currently works for me:

    Code:
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
            PropertySourcesPlaceholderConfigurer bean = new PropertySourcesPlaceholderConfigurer();        
            bean.setLocation(new FileSystemResource(System.getenv("MY_CONFIG")));
            return bean;
        }
    But I'm not sure if this is the best practice. In particular, the explicit lookup of the environment variable somehow doesn't feel right...

    Best regards,
    Harald

  • #2
    Hi Harald,

    You can @Inject the Environment into your @Configuration class and do a lookup against it:
    Code:
    @Configuration
    public class MyConfig {
        @Inject Environment env;
    
        @Bean
        public FooService fooService() {
            return new FooService(env.getProperty("some.property"));
        }
    }
    However, this exact approach won't work for you, because you are (as per the documentation) declaring your PropertySourcesPlaceholderConfigurer as 'static', i.e. you won't have access to the @Inject'ed instance variable 'env'. As an alternative, you can parameter-inject the Environment into the propertyConfigurer method:
    Code:
    @Configuration
    public class MyConfig {
        @Bean
        public PropertySourcesPlaceholderConfigurer propertyConfigurer(Environment env) {
            PropertySourcesPlaceholderConfigurer bean = new PropertySourcesPlaceholderConfigurer();        
            bean.setLocation(new FileSystemResource(env.getProperty("MY_CONFIG")));
            return bean;
        }
    }
    Now, with that said, the most important question may be whether you need a Property(Sources)PlaceholderConfigurer at all. If, as you say, you are moving to a Java-only configuration situation, then ${...} replacement may not make much sense. Just as shown above, any time you need access to a property, you can simply resolve it from an @Inject'ed property (as opposed to, for example, @Value("${...}") annotations.

    See the "Working with externalized values" section of the Javadoc for @Configuration for fuller examples and explanations. http://static.springsource.org/sprin...iguration.html

    Let us know how it goes!

    Comment


    • #3
      Hi Chris,

      thanks for your examples! Injecting an Environment, I can indeed work without a PropertySourcesPlaceholderConfigurer like this:

      Code:
      @Configuration
      @PropertySource("file:${MY_CONFIG}")
      public class TcmDataSourceSpringConfig {
      
          @Inject
          private Environment env;
      
          @Bean
          public DataSource myDataSource() {
      
              String host = env.getProperty("myapp.jdbc.host");
              String database = env.getProperty("myapp.jdbc.database");
              String username = env.getProperty("myapp.jdbc.username");
              String password = env.getProperty("myapp.jdbc.password");
      
              String url = String.format("jdbc:mysql://%s/%s", host, database);
              DriverManagerDataSource ds = new DriverManagerDataSource(url);
              ds.setUsername(username);
              ds.setPassword(password);
              return ds;
          }
      }
      It's good to know that the @PropertySource annotation is able to interpolate a system environment variable - I was afraid this might not work but it does :-)

      Now I'm just wondering if it's possible to combine this new style @PropertySource configuration with old-style @Value injection?

      Code:
      @Value("${myapp.jdbc.host"}
      private String host;
      is a bit more concise than using env.getProperty(...) all over the place, and putting all the @Value-annotated members at the top of your configuration class, you can easily see all the external properties that your configuration depends on.

      I've tried this, but the ${...} expression does not get interpolated.

      On a side note, I'm not sure if it's common practice to reference a properties file by an environment variable, and maybe this can also be simplified with some of the new Spring 3.1 features. The reasons for taking this approach are:
      • Our properties contain host specific settings. So we can't reference them from the classpath (which would amount to packaging the properties file into the JAR or WAR).
      • We can't use a @PropertySource("file:/path/to/myapp.properties") because file system structures vary from host to host.
      • We can't use a @PropertySource("file:${my.config}")) where my.config is a Java system property because this does not work well when running JUnit tests interactively from Eclipse - you'd have to manually add this system property to the launcher for every single test.
      Indirectly referencing a properties file via a system environment variable is the only way we've found so far to cover all development and production scenarios, working with or without Maven and with or without Eclipse.

      Maybe not elegant, but it works... Any ideas for improvement are welcome!

      Best regards,
      Harald

      Comment


      • #4
        Originally posted by hwellmann View Post
        Now I'm just wondering if it's possible to combine this new style @PropertySource configuration with old-style @Value injection?

        Code:
        @Value("${myapp.jdbc.host}")
        private String host;
        I've tried this, but the ${...} expression does not get interpolated.
        For this you must register a PropertySourcesPlaceholderConfigurer, which as the name suggests, will search your PropertySources when trying to do the replacement.


        On a side note, I'm not sure if it's common practice to reference a properties file by an environment variable, and maybe this can also be simplified with some of the new Spring 3.1 features. The reasons for taking this approach are:
        • Our properties contain host specific settings. So we can't reference them from the classpath (which would amount to packaging the properties file into the JAR or WAR).
        • We can't use a @PropertySource("file:/path/to/myapp.properties") because file system structures vary from host to host.
        • We can't use a @PropertySource("file:${my.config}")) where my.config is a Java system property because this does not work well when running JUnit tests interactively from Eclipse - you'd have to manually add this system property to the launcher for every single test.
        Indirectly referencing a properties file via a system environment variable is the only way we've found so far to cover all development and production scenarios, working with or without Maven and with or without Eclipse.

        Maybe not elegant, but it works... Any ideas for improvement are welcome!
        Consider @PropertySource("${myapp.properties.path}").

        When processing this annotation, Spring will search all existing PropertySources (e.g. system properties, environment variables, jndi, context params, servlet init params) for a property named "myapp.properties.path".

        In production, you could set this with -Dmyapp.properties.path=file:///etc/myapp/myapp.properties

        In your JUnit tests, you could simply add a MockPropertySource to the ApplicationContext, where the mock property source contains this property:

        Code:
        ConfigurableApplicationContext ctx = ...
        ctx.getEnvironment().addPropertySource(new MockPropertySource().withProperty("myapp.properties.path", "classpath:test.properties")
        ctx.refresh();
        However, this might all be a bit of overkill. You don't have to use @PropertySource if it doesn't make sense to; it's just a convenience. You could instead add a PropertySource programmatically as necessary depending on your situation. If this is a webapp, you would add the property source using an ApplicationContextInitializer, or if you have Servlet 3.0, perhaps a WebApplicationInitializer. If it's a standalone application, you'd do it as listed above wherever you instantiate the ApplicationContext. In JUnit tests, you'd do it as above too.

        HTH,

        Chris

        Comment


        • #5
          Originally posted by Chris Beams View Post

          Consider @PropertySource("${myapp.properties.path}").

          When processing this annotation, Spring will search all existing PropertySources (e.g. system properties, environment variables, jndi, context params, servlet init params) for a property named "myapp.properties.path".

          In production, you could set this with -Dmyapp.properties.path=file:///etc/myapp/myapp.properties
          Ok, moving the "file:" protocol prefix from the @PropertySource annotation value to the property value itself provides some more flexibility.

          Originally posted by Chris Beams View Post
          In your JUnit tests, you could simply add a MockPropertySource to the ApplicationContext, where the mock property source contains this property:

          Code:
          ConfigurableApplicationContext ctx = ...
          ctx.getEnvironment().addPropertySource(new MockPropertySource().withProperty("myapp.properties.path", "classpath:test.properties")
          ctx.refresh();
          I can see how this works, but I don't think it's manageable. I have a very large number of JUnit test classes, all of which use the same context configuration (at least per subproject/module), like this.


          Code:
          @RunWith(SpringJUnit4ClassRunner.class)
          @ContextConfiguration(classes = { MyAppTestConfig.class })
          @Transactional
          public class FoobarTest {
          
              @Test
              public void doSomething() {
                  // ...
              }
          }
          I don't even see this ConfigurableApplicationContext. I suppose I could simply inject it, but I don't want to add this ctx.getEnvironment().addPropertySource() to each and every test class, and using a base class for all tests would be a regression into Spring 2.5 idioms. So it seems I'd better stick with the environment variable for bootstrapping the configuration...

          Best regards,
          Harald

          Comment


          • #6
            Hi Harald,

            I thought you might say as much with regard to @ContextConfiguration use

            This is probably worth adding an issue to JIRA - provide support for adding/interacting with property sources when using TestContext framework - please feel free to do this.

            In the meantime, it sounds like the first option will work for you.

            Cheers,

            Chris

            Comment

            Working...
            X