Spring tips: How to merge collections in spring xml?

Hello guys, today we are going to discover how to merge collections in spring.xml. So lets imagine the situation that you are developing 2 a bit different websites “potatoSite” and “tomatoSite” using spring. And you created a a service that clean ups data.

interface CleanUpService {
    void cleanUp();
    void setDirsToCleanUp(Set<String> dirs);
}

You made your service configurable so you can set up list of directories to clean up. So the initial setup was following:

<bean id="cleanUpService" class="x.y.z.CleanUpServiceImpl">
	<property name="dirsToCleanUp">
		<set>
			<value>media</value>
			<value>logs</value>
		</set>
	</property>
</bean>

But then a new requirement appeared and now for different sites you have to use 2 different set of directories. Since you already had profiles for different sites you decided to put beans under profile:

<beans profile="tomatoSite">
    <bean id="cleanUpService" class="x.y.z.CleanUpServiceImpl">
        <property name="dirsToCleanUp">
            <set>
                <value>media</value>
                <value>logs</value>
                <value>archived</value>
            </set>
        </property>
    </bean>
</beans>

<beans profile="potatoSite">
    <bean id="cleanUpService" class="x.y.z.CleanUpServiceImpl">
        <property name="dirsToCleanUp">
            <set>
                <value>media</value>
                <value>logs</value>
                <value>cache</value>
            </set>
        </property>
    </bean>
</beans>

It looks good, does not it? Everything what is required implemented. It seems that we can stop. But no, we have a duplication of configs. So we have a choice:

  • to create a parent bean for service with common set of dirs and extend it in child beans under spring profile
  • to create a parent bean for sets and extend it in child beans.

Lets consider both options. The first is option with service as a parent bean and extending dirToCleanUp property in child beans:

<bean id="baseCleanUpService" class="x.y.z.CleanUpServiceImpl" abstract="true">
    <property name="dirsToCleanUp">
        <set>
            <value>media</value>
            <value>logs</value>
        </set>
    </property>
</bean>

<beans profile="tomatoSite">
    <bean id="cleanUpService" parent="baseCleanUpService">
        <property name="dirsToCleanUp">
            <set merge="true">
                <value>archived</value>
            </set>
        </property>
    </bean>
</beans>

<beans profile="potatoSite">
    <bean id="cleanUpService" parent="baseCleanUpService">
        <property name="dirsToCleanUp">
            <set merge="true"> 
                <value>cache</value> 
             </set> 
        </property> 
    </bean> 
</beans> 

Merge attribute is presented for list, map, set or props elements. It covers the most cases you have in spring application.

The way I showed above is more classical way, sometimes you can’t follow it e.g. you don’t have spring profiles, but you need to implement such feature. What you going to do? I assume solution can be the following:

<bean id="cleanUpService" class="x.y.z.CleanUpServiceImpl">
    <property name="dirsToCleanUp" ref="${site.id}-dirs"/>
</bean>

<bean id="commonDirs" class="org.springframework.beans.factory.config.SetFactoryBean">
    <property name="sourceSet">
        <set>
            <value>media</value>
            <value>logs</value>
        </set>
    </property>
</bean>

<bean id="popato-dirs" parent="commonDirs">
    <property name="sourceSet">
        <set merge="true">
            <value>cache</value>
        </set>
    </property>
</bean>

<bean id="tomato-dirs" parent="commonDirs">
    <property name="sourceSet">
        <set merge="true">
            <value>archived</value>
        </set>
    </property>
</bean>

The snippet above has 2 interesting things:

  • You can use FactoryBean to inherit set property. Know child classes of Factory bean are: ListFactoryBean, MapFactoryBean, SetFactoryBean, SortedResourcesFactoryBean etc.
  • You can use values from property file to define bean name. In example above ${site.id}-dirs will be evaluated dynamically based on included source of .properties file and if you have site.id=tomato that tomato-dirs bean will be used.

Basically that is it for today. I understand that situation may look like unreal and I guess most of developer would just extract list of directories into property file, but now you know that such feature in spring exist and you can use it. Thanks.