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

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.

Answers


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


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.

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 :)


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


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.


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.


Another one to consider for Spring 3.0:

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

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


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.


Need Your Help

Jasmine - Spying on a method call within a constructor

javascript testing jasmine spy

I want to test whether the following method is called with in my Javascript object constructor. From what I have seen in the Jasmine documentation, I can spy on a constructor method and I can spy on

How to get NEW width/height of root layout in onConfigurationChanged?

android orientation scrollview onconfigurationchanged

One of our views has a ScrollView as its root layout. When the device is rotated and onConfigurationChanged() is called, we'd like to be able to get the ScrollView's new width/height. Our code look...