springspring-beanproperty-placeholder

Accessing spring loaded properties in BeanDefinitionRegistryPostProcessor


How can I access properties loaded by <context:property-placeholder> in BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry.

I am unable to use fields annotated with @Value, as they do not seem to be initialized (their values are null).


Solution

  • Setting the value of fields annotated with @Value happens only after the post-processing of the BeanDefinitionRegistry, meaning they are not usable at this stage of the initialization process.

    You can however explicitly scan the configuration environment and read the relevant properties' values from there, then use them in your dynamic bean definitions.

    To gain access to the configuration environment, you can create your BeanDefinitionRegistryPostProcessor in a method annotated with @Bean, that takes the ConfigurableEnvironment as a parameter.

    See the following example:

    package com.sample.spring;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.core.env.EnumerablePropertySource;
    import org.springframework.core.env.PropertySource;
    
    @Configuration
    public class DynamicBeanConfig {
    
        private static final String PROPERTY_KEY = "somename"; 
    
        @Bean
        public BeanDefinitionRegistryPostProcessor beanPostProcessor(ConfigurableEnvironment environment) {
            return new PostProcessor(environment);
        }
    
        class PostProcessor implements BeanDefinitionRegistryPostProcessor {
    
            private String propertyValue;
    
            /*
             * Reads property value from the configuration, then stores it
             */
            public PostProcessor(ConfigurableEnvironment environment) {
                propertyValue = readProperty(environment);
            }
    
            @Override
            public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
    
            /*
             * Creates the bean definition dynamically (using the configuration value), then registers it
             */
            @Override
            public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SampleDynamicBean.class);
                builder.addPropertyValue("property", propertyValue);
                registry.registerBeanDefinition("sampleDynamicBean", builder.getBeanDefinition());
            }
    
            /*
             * Iterates over all configuration sources, looking for the property value.
             * As Spring orders the property sources by relevance, the value of the first 
             * encountered property with the correct name is read and returned.
             */
            private String readProperty(ConfigurableEnvironment environment) {
                for (PropertySource<?> source : environment.getPropertySources()) {
                    if (source instanceof EnumerablePropertySource) {
                        EnumerablePropertySource<?> propertySource = (EnumerablePropertySource<?>) source;
                        for (String property : propertySource.getPropertyNames()) {
                            if (PROPERTY_KEY.equals(property))
                            {
                                return (String)propertySource.getProperty(PROPERTY_KEY);
                            }
                        }
                    }
                }
                throw new IllegalStateException("Unable to determine value of property " + PROPERTY_KEY);
            }
    
        }
    
        class SampleDynamicBean {
            private String property;
    
            public void setProperty(String property)
            {
                this.property = property;
            }
    
            public String getMessage()
            {
                return "This message is produced by a dynamic bean, it includes " + property;
            }
    
        }
    
    }
    

    The sample code is adapted from this blog post, https://scanningpages.wordpress.com/2017/07/28/spring-dynamic-beans/