Jonathan Martin
Jonathan Martin

Reputation: 71

How to build custom PropertySource for binding to @ConfigurationProperties

We're creating a new PropertySource that uses a database as it's repository. The idea is that we can update property values at runtime.

At the same time, we'd like to use @ConfigurationProperties to so we can include validation as well as use the application-{profile} naming conventions.

However, it appears that values of @ConfigurationProperties are only loaded from "applicationConfig: [path/to/config]" PropertySource's. For example, the following test:

private final String OVERRIDEN_VALUE = "overriden value";

@Before
public void before() {

    LOGGER.debug("Property sources are: "); 
    for(Iterator<?> it = env.getPropertySources().iterator(); it.hasNext(); ) {
        PropertySource<?> propertySource = (PropertySource<?>) it.next();
        LOGGER.debug(propertySource.getName());
    }

    EnvironmentTestUtils.addEnvironment("integrationTest", env, "some.prefix.overridable-property=" + OVERRIDEN_VALUE);

}

@Test
public void testOverridingDefaultProperties() {

    LOGGER.debug("MutablePropertySources value: {}", env.getProperty("some.prefix.overridable-property"));
    LOGGER.debug("@ConfigurationProperties value: {}", testProperties.getOverridableProperty());

    Assert.assertEquals(OVERRIDEN_VALUE, testProperties.getOverridableProperty());

}

Produces this output:

Property sources are: 
    systemProperties
    systemEnvironment
    random
    integrationTest
    applicationConfig: [classpath:/path/to/my/application.yml]

MutablePropertySources value: overriden value
@ConfigurationProperties value: default value

For more context, I originally asked this question on Spring Boot's Github here.

Upvotes: 3

Views: 3638

Answers (1)

Jonathan Martin
Jonathan Martin

Reputation: 71

Thanks to the Spring Boot folks. Pointed me to Spring Cloud Context

http://projects.spring.io/spring-cloud/spring-cloud.html#customizing-bootstrap-property-sources

Looks like this will do the trick.

Update:

Since we already had the additional database-backed PropertySource written we really only needed to refresh the @ConfigurationProperties at runtime. At the same time we didn't want to refresh all @ConfigurationProperties. To accomplish the refresh we did the following:

  1. Created an annotation called @ReloadableProperties
  2. Created the following utility bean which utilizes Spring Boot's ConfigurationPropertiesBindingPostProcessor
/**
 * 
 * Helper bean to reload {@code @ConfigurationProperties}
 * if the {@code @ConfigurationProperties} bean is annotated 
 * with {@code @ReloadableProperties}.
 * 
 * @author Jonathan Martin
 * @since 2.0.0
 * 
 * @see ReloadableProperties
 * @see ConfigurationPropertiesBindingPostProcessor
 *
 */
public class ConfigurationPropertiesReloader {

    private final ApplicationContext context;

    private final ConfigurationPropertiesBindingPostProcessor processor;

    @Autowired
    public ConfigurationPropertiesReloader(ApplicationContext context,  ConfigurationPropertiesBindingPostProcessor processor) {
        this.context = context;
        this.processor = processor;
    }

    /**
     * Reload all {@code @ConfigurationProperties}
     * annotated with {@code @ReloadableProperties}.
     */
    public void reload() {
        Map beans = context.getBeansWithAnnotation(ReloadableProperties.class);
        for (Map.Entry entry : beans.entrySet()) {

            String beanName = entry.getKey();
            Object bean = entry.getValue();

            ConfigurationProperties annotation = AnnotationUtils.findAnnotation(bean.getClass(), ConfigurationProperties.class);

            // Only reload the bean if it's an @ConfigurationProperties
            // Can't check for instance of ConfigurationPropertiesHolder 
            // because it uses package scope.
            if (annotation != null) {
                processor.postProcessBeforeInitialization(bean, beanName);
            }

        }
    }

}

Now if I inject the utility bean into my test and invoke reload, the test passes.

Upvotes: 2

Related Questions