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.