Thursday, August 09, 2012

Spring: Dynamic Import with Properties-configured Resource Path


     As we know, it's not allowed to pass properties variables in import tag in application context. So here I use a simple solution -- create a sub application context.

First, let's see what the parent application context looks like:

<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean id="my.propertyPlaceholderConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:/foo/my.properties</value>
            </list>
        </property>
    </bean>

    <import resource="classpath:META-INF/foo/bundle-service.xml"/>
    
    <bean id="my.applicationContextLoader"
          class="foo.config.ApplicationContextLoader"
          init-method="initialize"
          destroy-method="close">
        <property name="propertyPlaceholder" ref="my.propertyPlaceholderConfigurer"/>
        <property name="contextLocations">
            <list>
                <value>classpath:META-INF/foo/bundle-jms-${jms.server.type}.xml</value>
                <value>classpath:META-INF/foo/bundle-jms-service.xml</value>
            </list>
        </property>
    </bean>

</beans>

You can see that in my.applicationContextLoader I can use different kinds of JMS application contexts depending on the value of jms.server.type in properties. It could be hornetq or activemq. I guess you already understand how it works now.

So let's briefly see how ApplicationContextLoader implements:

public class ApplicationContextLoader implements ApplicationContextAware {
    ...
    
    public void initialize() throws Exception {
        loadedAppContext = new GenericApplicationContext(applicationContext);

        XmlBeanDefinitionReader beanReader = null;
        beanReader = new XmlBeanDefinitionReader(loadedAppContext);

        /* only needed when those settings in spring jar are invisible, e.g. in OSGi platform 
        beanReader.setNamespaceHandlerResolver(new DefaultNamespaceHandlerResolver(
                applicationContext.getClassLoader(), YOUR_HANDLER_MAPPINGS_LOCATION));
        beanReader.setEntityResolver(new PluggableSchemaResolver(
                applicationContext.getClassLoader(), YOUR_SCHEMA_MAPPINGS_LOCATION));
        */

        for (Resource contextLocation : contextLocations) {
            beanReader.loadBeanDefinitions(contextLocation);
        }

        if (propertyPlaceholder != null) {
            propertyPlaceholder.postProcessBeanFactory(loadedAppContext.getBeanFactory());
        }

        loadedAppContext.refresh();
    }

    public void close() {
        if (loadedAppContext != null) {
            loadedAppContext.close();
        }
    }
}

That's it.

Note that remember that sub application context should avoid importing contexts in parent application context. This will cause beans being initialized twice even it's singleton.