Spring @Configuration file with PropertyPlaceholde

2019-02-02 07:27发布

问题:

I have following configuration file:

@Configuration
public class PropertyPlaceholderConfigurerConfig {

    @Value("${property:defaultValue}")
    private String property;

    @Bean
    public static PropertyPlaceholderConfigurer ppc() throws IOException {
        PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
        ppc.setLocations(new ClassPathResource("properties/" + property + ".properties"));
        ppc.setIgnoreUnresolvablePlaceholders(true);
        return ppc;
    }
}

I run my application with following VM option:

-Dproperty=propertyValue

So I'd like my application to load specific property file on startup. But for some reason at this stage @Value annotations are not processed and property is null. On the other hand if I have PropertyPlaceholderConfigurer configured via xml file - everything works perfectly as expected. Xml file example:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreResourceNotFound" value="true"/>
    <property name="location">
        <value>classpath:properties/${property:defaultValue}.properties</value>
    </property>
</bean>

If I try to to inject property value in another Spring configuration file - it is properly injected. If I move my PropertyPlaceholderConfigurer bean creation to that configuration file - field value is null again.

As workaround, I use this line of code:

System.getProperties().getProperty("property", "defaultValue")

Which is also works, but I'd like to know why such behavior is occurs and maybe it is possible to rewrite it in other way but without xml?

回答1:

From Spring JavaDoc:

In order to resolve ${...} placeholders in definitions or @Value annotations using properties from a PropertySource, one must register a PropertySourcesPlaceholderConfigurer. This happens automatically when using in XML, but must be explicitly registered using a static @Bean method when using @Configuration classes. See the "Working with externalized values" section of @Configuration's javadoc and "a note on BeanFactoryPostProcessor-returning @Bean methods" of @Bean's javadoc for details and examples.

So, you are trying to use a placeholder in the code block required to enable placeholder processing.

As @M.Deinum mentioned, you should use a PropertySource (default or custom implementation).

Example below shows how to use properties in a PropertySource annotation as well as how to inject properties from the PropertySource in a field.

@Configuration
@PropertySource(
          value={"classpath:properties/${property:defaultValue}.properties"},
          ignoreResourceNotFound = true)
public class ConfigExample {

    @Value("${propertyNameFromFile:defaultValue}")
    String propertyToBeInjected;

    /**
     * Property placeholder configurer needed to process @Value annotations
     */
     @Bean
     public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
     }
}


回答2:

For any other poor souls who couldn't get this to work in some Configuration classes when they work in others:

Look to see what other beans you have in that class and if any of them get instantiated early in the ApplicationContext. A ConversionService is an example of one. This would instantiate the Configuration class before what is required is registered, thereby making no property injection take place.

I fixed this by moving the ConversionService to another Configuration class that I Imported.



回答3:

If you run your application using VM option and then want to access that option in your application you have to do it slightly different:

@Value("#{systemProperties.property}")
private String property;

Your PropertyPlaceholderConfigurer is not aware of system properties, also note that you are accessing properties using $ - which refers to place holders and # refers to beans, where systemProperties is a bean.