Spring Collection Merging Example

0

If the parent bean contains a list, map, set or props properties, the child bean extending the parent bean automatically inherits the parent bean’s collection element.

The child bean can override the parent collection, by providing its own collection values. The child collection can either simply replace the parent’s collection or merge to it its own values.

The Spring supports the merging of collections using default-merge and merge attributes. In this article, I will show you some examples of collection merging.

This example uses the following frameworks:

  1. Maven 3.2.3
  2. Spring 4.1.5.RELEASE
  3. Eclipse  as the IDE, version Luna 4.4.1.

Dependencies

Add the following dependencies:

  1. spring-core
  2. spring-context

Tutorial and a List of Topics

We will use Tutorial bean to understand the merge attribute behavior. A Tutorial bean consists of a list of topics and a map of hot topics.

Tutorial:

package com.javarticles.spring;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Tutorial {
    private String name;
    
    private List topicsList = new ArrayList<>();
    private Map<String, String> hotTopics = new HashMap<String, String>();
    
    public Tutorial(){}
    
    public Tutorial(List topicsList){
        this.topicsList = topicsList;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }   

    public List getTopicsList() {
        return topicsList;
    }

    public void setTopicsList(List topicsList) {
        this.topicsList = topicsList;
    }

    public String toString() {
        return name + topicsList;
    }

    public Map<String, String> getHotTopics() {
        return hotTopics;
    }

    public void setHotTopics(Map<String, String> hotTopics) {
        this.hotTopics = hotTopics;
    }
    
}

Without default-merge Attribute

default-merge attribute is defined at beans level. default-merge is not a mandatory attribute. Without the default-merge attribute, the collection won’t be merged.

noMergeAttributeContext.xml:

<?xml version="1.0" encoding="UTF-8"?>

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

	<bean id="javaCoreTopics" class="com.javarticles.spring.Tutorial">	
		<property name="name" value="Java Core Topics"/>	
		<property name="topicsList">
			<list>
				<value>Java Core</value>
				<value>Java Concurrency</value>
			</list>
		</property>
	</bean>

	<bean id="javaEnterpriseTopics" parent="javaCoreTopics">
		<property name="name" value="Java Enterprise Topics"/>
		<property name="topicsList">
			<list>
				<value>EJB</value>
				<value>Servlet</value>
			</list>
		</property>
	</bean>
</beans>

SpringByDefaultNoMergeExample:

package com.javarticles.spring;

import java.util.List;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringByDefaultNoMergeExample {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "noMergeAttributeContext.xml");
        try {
            System.out.println("'default-merge' and 'merge' attributes are not there. Default behavior should be 'no' merge.");
            Tutorial javaCoreTutorial = (Tutorial) context.getBean("javaCoreTopics");
            Tutorial javaEnterpriseTutorial = (Tutorial) context.getBean("javaEnterpriseTopics");
            verifyListMerged(javaEnterpriseTutorial, javaCoreTutorial);            
        } finally {
            context.close();
        }
    }
    
    private static void verifyListMerged(Tutorial javaSubGroup, Tutorial javaCore) {
        List javaEnterpriseTopics = javaSubGroup.getTopicsList();
        List javaCoreTopics = javaCore.getTopicsList();
        System.out.println(javaSubGroup.getName() + " contain " + javaCore.getName() + "? " + 
        javaEnterpriseTopics.containsAll(javaCoreTopics));
        System.out.println(javaSubGroup.getName() + ": " + javaEnterpriseTopics);
    }
}

Output:

'default-merge' and 'merge' attributes are not there. Default behavior should be 'no' merge.
Java Enterprise Topics contain Java Core Topics? false
Java Enterprise Topics: [EJB, Servlet]

Spring default-merge Example

In this example, default-merge is set to true so the child tutorial bean will have its collection merged to parent’s tutorial.
defaultMergeAttributeContext.xml:

<?xml version="1.0" encoding="UTF-8"?>

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

	<bean id="javaCoreTopics" class="com.javarticles.spring.Tutorial">
	    <property name="name" value="Java Core Topics"/>
		<property name="topicsList">
			<list>
				<value>Java Core</value>
				<value>Java Concurrency</value>
			</list>
		</property>
	</bean>

	<bean id="javaEnterpriseTopics" parent="javaCoreTopics">
		<property name="name" value="Java Enterprise Topics"/>
		<property name="topicsList">
			<list>
				<value>EJB</value>
				<value>Servlet</value>
			</list>
		</property>
	</bean>
</beans>

SpringDefaultMergeExample:

package com.javarticles.spring;

import java.util.List;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDefaultMergeExample {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "defaultMergeAttributeContext.xml");
        try {
            System.out.println("default-merge is set to true. List of enterprise topics should get merged to parent(java topics).");
            Tutorial javaCoreTutorial = (Tutorial) context.getBean("javaCoreTopics");
            Tutorial javaEnterpriseTutorial = (Tutorial) context.getBean("javaEnterpriseTopics");
            verifyListMerged(javaEnterpriseTutorial, javaCoreTutorial);            
        } finally {
            context.close();
        }
    }
    
    private static void verifyListMerged(Tutorial javaSubGroup, Tutorial javaCore) {
        List javaEnterpriseTopics = javaSubGroup.getTopicsList();
        List javaCoreTopics = javaCore.getTopicsList();
        System.out.println(javaSubGroup.getName() + " contain " + javaCore.getName() + "? " + 
        javaEnterpriseTopics.containsAll(javaCoreTopics));
        System.out.println(javaSubGroup.getName() + ": " + javaEnterpriseTopics);
    }
}

Output:

'default-merge' and 'merge' attributes are not there. Default behavior should be 'no' merge.
Java Enterprise Topics contain Java Core Topics? false
Java Enterprise Topics: [EJB, Servlet]

Override default-merge attribute

In this example, we will using merge attribute in the child tutorial to override the default-merge attribute.

overrideMergeAttributeContext.xml:

<?xml version="1.0" encoding="UTF-8"?>

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

	<bean id="javaCoreTopics" class="com.javarticles.spring.Tutorial">
		<property name="name" value="Java Core Topics"/>
		<property name="topicsList">
			<list>
				<value>Java Core</value>
				<value>Java Concurrency</value>
			</list>
		</property>
	</bean>

	<bean id="javaEnterpriseTopics" parent="javaCoreTopics">
		<property name="name" value="Java Enterprise Topics"/>
		<property name="topicsList">
			<list merge="true">
				<value>EJB</value>
				<value>Servlet</value>
			</list>
		</property>
	</bean>
	
	<bean id="javaGenericsTopics" parent="javaCoreTopics">
		<property name="name" value="Java Generics"/>
		<property name="topicsList">
			<list>
				<value>javaGenericsBasic</value>
				<value>javaGenericsAdvanced</value>
			</list>
		</property>
	</bean>
</beans>

SpringOverrideMergeExample:

package com.javarticles.spring;

import java.util.List;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringOverrideMergeExample {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "overrideMergeAttributeContext.xml");
        try {
            System.out.println("'default-merge=false' is overriden by list's 'merge=true'");
            Tutorial javaCoreTutorial = (Tutorial) context.getBean("javaCoreTopics");
            Tutorial javaEnterpriseTutorial = (Tutorial) context.getBean("javaEnterpriseTopics");
            verifyListMerged(javaEnterpriseTutorial, javaCoreTutorial);      
            
            System.out.println("'default-merge=false' is not overriden so list won't get merged");
            Tutorial javaGenericsTutorial = (Tutorial) context.getBean("javaGenericsTopics");
            verifyListMerged(javaGenericsTutorial, javaCoreTutorial);  
        } finally {
            context.close();
        }
    }
    
    private static void verifyListMerged(Tutorial javaSubGroup, Tutorial javaCore) {
        List javaEnterpriseTopics = javaSubGroup.getTopicsList();
        List javaCoreTopics = javaCore.getTopicsList();
        System.out.println(javaSubGroup.getName() + " contain " + javaCore.getName() + "? " + 
        javaEnterpriseTopics.containsAll(javaCoreTopics));
        System.out.println(javaSubGroup.getName() + ": " + javaEnterpriseTopics);
    }
}

Output:

'default-merge=false' is overriden by list's 'merge=true'
Java Enterprise Topics contain Java Core Topics? true
Java Enterprise Topics: [Java Core, Java Concurrency, EJB, Servlet]
'default-merge=false' is not overriden so list won't get merged
Java Generics contain Java Core Topics? false
Java Generics: [javaGenericsBasic, javaGenericsAdvanced]

Override default-merge in Nested Beans

We can override the default-merge using another default-merge attribute in the nested beans.
nestedBeansMergeAttributeContext.xml:

<?xml version="1.0" encoding="UTF-8"?>

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

	<bean id="javaCoreTopics" class="com.javarticles.spring.Tutorial">
		<property name="name" value="Java Core Topics" />
		<property name="topicsList">
			<list>
				<value>Java Core</value>
				<value>Java Concurrency</value>
			</list>
		</property>
	</bean>

	<beans default-merge="true">
		<bean id="javaEnterpriseTopics" parent="javaCoreTopics">
			<property name="name" value="Java Enterprise Topics" />
			<property name="topicsList">
				<list>
					<value>EJB</value>
					<value>Servlet</value>
				</list>
			</property>
		</bean>

		<bean id="javaGenericsTopics" parent="javaCoreTopics">
			<property name="name" value="Java Generics" />
			<property name="topicsList">
				<list>
					<value>javaGenericsBasic</value>
					<value>javaGenericsAdvanced</value>
				</list>
			</property>
		</bean>
	</beans>
</beans>

SpringNestedBeansMergeExample:

package com.javarticles.spring;

import java.util.List;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringNestedBeansMergeExample {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "nestedBeansMergeAttributeContext.xml");
        try {
            System.out.println("'default-merge=false' is overriden by nested bean's 'default-merge=true'");
            Tutorial javaCoreTutorial = (Tutorial) context.getBean("javaCoreTopics");
            Tutorial javaEnterpriseTutorial = (Tutorial) context.getBean("javaEnterpriseTopics");
            verifyListMerged(javaEnterpriseTutorial, javaCoreTutorial);      
            
            Tutorial javaGenericsTutorial = (Tutorial) context.getBean("javaGenericsTopics");
            verifyListMerged(javaGenericsTutorial, javaCoreTutorial);  
        } finally {
            context.close();
        }
    }
    
    private static void verifyListMerged(Tutorial javaSubGroup, Tutorial javaCore) {
        List javaEnterpriseTopics = javaSubGroup.getTopicsList();
        List javaCoreTopics = javaCore.getTopicsList();
        System.out.println(javaSubGroup.getName() + " contain " + javaCore.getName() + "? " + 
        javaEnterpriseTopics.containsAll(javaCoreTopics));
        System.out.println(javaSubGroup.getName() + ": " + javaEnterpriseTopics);
    }
}

Output:

'default-merge=false' is overriden by nested bean's 'default-merge=true'
Java Enterprise Topics contain Java Core Topics? true
Java Enterprise Topics: [Java Core, Java Concurrency, EJB, Servlet]
Java Generics contain Java Core Topics? true
Java Generics: [Java Core, Java Concurrency, javaGenericsBasic, javaGenericsAdvanced]

Merge list in Constructor

We can also merge the list passed as the constructor argument.

constructorListMergeContext.xml:

<?xml version="1.0" encoding="UTF-8"?>

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

	<bean id="javaCoreTopics" class="com.javarticles.spring.Tutorial">
		<property name="name" value="Java Core Topics"/>
		<property name="topicsList">
			<list>
				<value>Java Core</value>
				<value>Java Concurrency</value>
			</list>
		</property>
	</bean>

	<bean id="javaEnterpriseTopics" parent="javaCoreTopics">
		<property name="name" value="Java Enterprise Topics"/>
		<constructor-arg name="topicsList">
			<list merge="true">
				<value>EJB</value>
				<value>Servlet</value>
			</list>
		</constructor-arg>
	</bean>	
</beans>

SpringConstructorListMergeExample:

package com.javarticles.spring;

import java.util.List;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringConstructorListMergeExample {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "constructorListMergeContext.xml");
        try {
            System.out.println("'default-merge=false' is overriden by constructor list argument with 'merge=true'");
            Tutorial javaCoreTutorial = (Tutorial) context.getBean("javaCoreTopics");
            Tutorial javaEnterpriseTutorial = (Tutorial) context.getBean("javaEnterpriseTopics");
            verifyListMerged(javaEnterpriseTutorial, javaCoreTutorial);            
        } finally {
            context.close();
        }
    }
    
    private static void verifyListMerged(Tutorial javaSubGroup, Tutorial javaCore) {
        List javaEnterpriseTopics = javaSubGroup.getTopicsList();
        List javaCoreTopics = javaCore.getTopicsList();
        System.out.println(javaSubGroup.getName() + " contain " + javaCore.getName() + "? " + 
        javaEnterpriseTopics.containsAll(javaCoreTopics));
        System.out.println(javaSubGroup.getName() + ": " + javaEnterpriseTopics);
    }
}

Output:

'default-merge=false' is overriden by constructor list argument with 'merge=true'
Java Enterprise Topics contain Java Core Topics? true
Java Enterprise Topics: [Java Core, Java Concurrency]

Merging map Example

In this example, we will merge map of hot topics to the all time map of hot topics. The all time hot topics are defined by the child bean allTimeHotTopics. The map of topics will be merged to parent bean currentHotTopics.

mapMergeContext.xml:

<?xml version="1.0" encoding="UTF-8"?>

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

	<bean id="currentHotTopics" class="com.javarticles.spring.Tutorial">
		<property name="name" value="Current Hot Topics"/>
		<property name="hotTopics">
			<map>
				<entry key="java" value="Lambda Expressions"/>
				<entry key="functionalLang" value="Scala"/>
				<entry key="performance" value="Database Performance"/>
			</map>
		</property>
	</bean>

	<bean id="allTimeHotTopics" parent="currentHotTopics">
		<property name="name" value="All Time Hot Topics"/>
		<property name="hotTopics">
			<map merge="true">
				<entry key="java" value="Java 8 in Nutshell"/>
				<entry key="functionalLang" value="Scala"/>
				<entry key="bigData" value="Casandra"/>
			</map>
		</property>
	</bean>
</beans>

SpringMapMergeExample:

package com.javarticles.spring;

import java.util.Map;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringMapMergeExample {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "mapMergeContext.xml");
        try {
            System.out.println("'default-merge=false' is overriden by map's 'merge=true'");
            Tutorial currentHotTopicsTutorial = (Tutorial) context.getBean("currentHotTopics");
            Tutorial allTimeHotTopicsTutorial = (Tutorial) context.getBean("allTimeHotTopics");
            verifyMapMerged(currentHotTopicsTutorial, allTimeHotTopicsTutorial);      
        } finally {
            context.close();
        }
    }
    
    private static void verifyMapMerged(Tutorial currentHotTopicsTutorial, Tutorial allTimeHotTopicsTutorial) {
        System.out.println(currentHotTopicsTutorial.getName() + ": " + currentHotTopicsTutorial.getHotTopics());
        Map<String, String> currentHotTopics = currentHotTopicsTutorial.getHotTopics();
        Map<String, String> allTimeHotTopics = allTimeHotTopicsTutorial.getHotTopics();
        System.out.println(currentHotTopicsTutorial.getName() + " contain " + allTimeHotTopicsTutorial.getName() + "? " + 
                allTimeHotTopics.keySet().containsAll(currentHotTopics.keySet()));
        System.out.println(allTimeHotTopicsTutorial.getName() + ": " + allTimeHotTopicsTutorial.getHotTopics());
    }
}

Output:

'default-merge=false' is overriden by map's 'merge=true'
Current Hot Topics: {java=Lambda Expressions, functionalLang=Scala, performance=Database Performance}
Current Hot Topics contain All Time Hot Topics? true
All Time Hot Topics: {java=Java 8 in Nutshell, functionalLang=Scala, performance=Database Performance, bigData=Casandra}

Download the source code

This was an example about spring collection merging. You can download the source code here: springCollectionMergeExample.zip

Share.

Comments are closed.