How to achieve conditional resource import in a Spring XML context?

JavaXmlSpring

Java Problem Overview


What I would like to achieve is the ability to "dynamically" (i.e. based on a property defined in a configuration file) enable/disable the importing of a child Spring XML context.

I imagine something like:

<import condition="some.property.name" resource="some-context.xml"/>

Where the property is resolved (to a boolean) and when true the context is imported, otherwise it isn't.

Some of my research so far:

  • Writing a custom NamespaceHandler (and related classes) so I can register my own custom element in my own namespace. For example: <myns:import condition="some.property.name" resource="some-context.xml"/>

    The problem with this approach is that I do not want to replicate the entire resource importing logic from Spring and it isn't obvious to me what I need to delegate to to do this.

  • Overriding DefaultBeanDefinitionDocumentReader to extend the behaviour of the "import" element parsing and interpretation (which happens there in the importBeanDefinitionResource method). However I'm not sure where I can register this extension.

Java Solutions


Solution 1 - Java

Prior to Spring 4, the closest you can get using standard Spring components is:

<import resource="Whatever-${yyzzy}.xml"/>

where ${xyzzy} interpolates a property from the system properties. (I use a hacky custom version of the context loader class that adds properties from other places to the system properties object before starting the loading process.)

But you can also get away with importing lots of unnecessary stuff ... and use various tricks to only cause the necessary beans to be instantiated. These tricks include:

  • placeholder and property substitution

  • selecting different beans using the new Spring expression language,

  • bean aliases with placeholders in the target name,

  • lazy bean initialization, and

  • smart bean factories.

Solution 2 - Java

This is now completely possible, using Spring 4.

In your main application content file

<bean class="com.example.MyConditionalConfiguration"/>

And the MyConditionalConfiguration looks like

@Configuration
@Conditional(MyConditionalConfiguration.Condition.class)
@ImportResource("/com/example/context-fragment.xml")
public class MyConditionalConfiguration {
    static class Condition implements ConfigurationCondition {
         @Override
         public ConfigurationPhase getConfigurationPhase() {
             return ConfigurationPhase.PARSE_CONFIGURATION;
         }
         @Override
         public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
             // only load context-fragment.xml if the system property is defined
             return System.getProperty("com.example.context-fragment") != null;
         }
    }
}

And then finally, you put the bean definitions you want included in the /com/example/context-fragment.xml

See the JavaDoc for @Conditional

Solution 3 - Java

As mentioned earlier, this can be easily accomplished with profiles if you're using Spring 3.1+

<!-- default configuration - will be loaded if no profile is specified -->
<!-- This will only work if it's put at the end of the configuration file -->
<!-- so no bean definitions after that -->
<beans profile="default">
    <import resource="classpath:default.xml" />
</beans>
<!-- some other profile -->
<beans profile="otherProfile">
	<import resource="classpath:other-profile.xml" />
</beans>

otherProfile can be easily activated with e.g.

mvn install -Dspring.profiles.active=otherProfile

if you're using different profiles in tests, just add -DforkMode=never to make sure that the tests will run inside same VM, therefore the param spring.profiles.active wont be lost

Solution 4 - Java

With Spring 3.1.x you can use bean profiles to achieve conditional resource import and bean instantiation. This is of course of no help if you are using an earlier version :)

Solution 5 - Java

For the record, Robert Maldon explains how to accomplish conditional definition of beans in this post: http://robertmaldon.blogspot.com/2007/04/conditionally-defining-spring-beans.html. It is a bit long to copy it here (besides, I don't think I should copy-paste his article anyway).

The end result with this approach, adapted for your example, is:

<condbean:cond test="${some.property.name}">
  <import resource="some-context.xml"/>
</condbean:cond>

It is certainly not so simple as Stephen C's solution, but it is much more poweful.

Solution 6 - Java

Another one to consider for Spring 3.0:

 <alias name="Whatever" alias=""Whatever-${yyzzy}" />

where ${xyzzy} interpolates a property from the system properties.

Solution 7 - Java

Another option is to have your app load a modules-config.xml file that is located in the /conf folder and edit it during the install/config phase to uncomment the modules you want loaded.

This is the solution I'm using with a web application that serves as a container for different integration modules. The web application is distributed with all the different integration modules. A modules-config.xml is placed in tomcat's /conf folder and the conf folder is added to the classpath (via catalina.properties/common.loader property). My web app webapp-config.xml has a <import resource="classpath:/modules-config.xml"/> to get it loaded.

Solution 8 - Java

You can override contextInitialized(javax.servlet.ServletContextEvent event) in your own ContextLoaderListener and set required System property before super.contextInitialized(event) called like this

package com.mypackage;
import org.springframework.web.context.ContextLoaderListener;
public class MyContextLoaderListener extends ContextLoaderListener {
    public void contextInitialized(javax.servlet.ServletContextEvent event) {
	    System.setProperty("xyz", "import-file-name.xml");
	    super.contextInitialized(event);
    }
}

And than replace ContextLoaderListener to MyContextLoaderListener in your web.xml

<listener>
	<listener-class>com.mypackage.MyContextLoaderListener</listener-class>
</listener>

Now you can use in your spring.xml

<import resource="${xyz}" /> 

I hope this will help.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBoris TerzicView Question on Stackoverflow
Solution 1 - JavaStephen CView Answer on Stackoverflow
Solution 2 - JavaptomliView Answer on Stackoverflow
Solution 3 - JavaMarko VranjkovicView Answer on Stackoverflow
Solution 4 - JavateabotView Answer on Stackoverflow
Solution 5 - JavaespinchiView Answer on Stackoverflow
Solution 6 - JavahawkeyeView Answer on Stackoverflow
Solution 7 - JavaScottDView Answer on Stackoverflow
Solution 8 - JavaszezsoView Answer on Stackoverflow