Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom spring property source does not resolve placeholders in @Value

I'm trying to build a Spring 3.1 PropertySource which reads its values from Zookeeper nodes. For connecting to Zookeeper I am using Curator from Netflix.

For that I've built a custom property source which reads the value of a property from Zookeeper and returns it. This works fine when I am resolving the property like this

ZookeeperPropertySource zkPropertySource = new ZookeeperPropertySource(zkClient);
ctx.getEnvironment().getPropertySources().addLast(zkPropertySource);
ctx.getEnvironment().getProperty("foo"); // returns 'from zookeeper'

However, when I try to instantiate a bean which has a field with an @Value annotation then this fails:

@Component
public class MyBean {
    @Value("${foo}") public String foo;
}

MyBean b = ctx.getBean(MyBean.class); // fails with BeanCreationException

This problem has most likely nothing to do with Zookeeper but with the way I'm registering the property sources and creating the beans.

Any insight is highly appreciated.

Update 1:

I'm creating the app context from an XML file like this:

public class Main {
    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ctx.registerShutdownHook();
    }
}

The class which connects to Zookeeper is a @Component.

@Component
public class Server {
    CuratorFramework zkClient;

    public void connectToZookeeper() {
        zkClient = ... (curator magic) ...
    }

    public void registerPropertySource() {
        ZookeeperPropertySource zkPropertySource = new ZookeeperPropertySource(zkClient);
        ctx.getEnvironment().getPropertySources().addLast(zkPropertySource);
        ctx.getEnvironment().getProperty("foo"); // returns 'from zookeeper'
    }

    @PostConstruct
    public void start() {
        connectToZookeeper();
        registerPropertySource();
        MyBean b = ctx.getBean(MyBean.class);
    }
}

Update 2

This seems to work when I'm using XML-less configuration, i.e. @Configuration, @ComponentScan and @PropertySource in combination with an AnnotationConfigApplicationContext. Why doesn't it work with a ClassPathXmlApplicationContext?

@Configuration
@ComponentScan("com.goleft")
@PropertySource({"classpath:config.properties","classpath:version.properties"})
public class AppConfig {
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}
like image 641
magiconair Avatar asked Aug 12 '12 21:08

magiconair


1 Answers

Answering to your Update 2: This does not work with your original configuration(registering a PropertySource using @PostConstruct) because the PropertySource is being registered very late, by this time your target bean has already been constructed and initialized.

Typically the injection of the placeholders happens via a BeanFactoryPostProcessor which is very early in the Spring lifecycle(beans have not been created at this stage) and if a PropertySource is registered at that stage, then placeholders should be resolved.

The best approach though is to use a ApplicationContextInitializer, get a handle on the applicationContext and to register the propertySource there:

public class CustomInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext> {
    public void initialize(ConfigurableWebApplicationContext ctx) {
        ZookeeperPropertySource zkPropertySource = new ZookeeperPropertySource(zkClient);
        ctx.getEnvironment().getPropertySources().addFirst(zkPropertySource);
    }
}
like image 110
Biju Kunjummen Avatar answered Sep 19 '22 02:09

Biju Kunjummen