Injecting Collections in Spring

1. Introduction

In this article we will discuss how to inject Collections using Spring. 

2. Content

If a Collection is publicly available then usually in plain Java we would create it under a class as a public static final object. Additionally we would like all the clients to use the unmodifiable copy of the Collection.  Below is how you can achieve that.

public class PublicCollection {

	private PublicCollection() {
	}

	public static final Set<String> COUNTRIES;
	static {
		Set<String> countries = new HashSet<>();
		countries.add("Netherlands");
		countries.add("France");
		countries.add("United States");
		countries.add("Canada");
		COUNTRIES = Collections.unmodifiableSet(countries);
	}

	public static final List<String> NAMES;
	static {
		List<String> names = new ArrayList<>();
		names.add("Ava");
		names.add("Emma");
		names.add("Benjamin");
		names.add("Bruce");
		NAMES = Collections.unmodifiableList(names);
	}

	public static final Map<String, String> SYMBOLS;
	static {
		Map<String, String> symbols = new HashMap<>();
		symbols.put("%", "Percent");
		symbols.put("*", "Asterisk");
		symbols.put("~", "Tilde");
		symbols.put("$", "Dollar");
		symbols.put("#", "Octothorpe");
		SYMBOLS = Collections.unmodifiableMap(symbols);
	}

}

An alternative to this approach would be to create a Collection as a Bean and inject it wherever needed. For purpose of this article, we will create a List, Set and Map bean and inject it using 3 different injection mechanisms.

3. Collections beans in Configuration class.

If we want to define Bean in Spring using annotations we use @Configuration annotation to the class. Then we just define beans in it. Package under which the Configuration class lies must be mentioned in @ComponentScan({“package name”})

3.1 Configuration class

Annotate the class with @Configuration.

@Configuration
public class CollectionConfiguration {

}

Create beans in this class. We will create 3 beans. One of type Set<String>, List<String> and Map<String, String>.

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CollectionConfiguration {

	@Bean("allCountries")
	public Set<String> countries() {
		Set<String> countries = new HashSet<>();
		countries.add("Netherlands");
		countries.add("France");
		countries.add("United States");
		countries.add("Canada");
		return Collections.unmodifiableSet(countries);
	}

	@Bean("names")
	public List<String> names() {
		List<String> names = new ArrayList<>();
		names.add("Ava");
		names.add("Emma");
		names.add("Benjamin");
		names.add("Bruce");
		return Collections.unmodifiableList(names);
	}

	@Bean("specialSymbols")
	public Map<String, String> symbols() {
		Map<String, String> symbols = new HashMap<>();
		symbols.put("%", "Percent");
		symbols.put("*", "Asterisk");
		symbols.put("~", "Tilde");
		symbols.put("$", "Dollar");
		symbols.put("#", "Octothorpe");
		return Collections.unmodifiableMap(symbols);
	}

}

Now you can inject using either of below mentioned ways :

import javax.inject.Inject;
import javax.inject.Named;

@Inject
@Named("allCountries") 
private Set<String> countries;
import javax.inject.Inject;
import org.springframework.beans.factory.annotation.Qualifier;

@Inject
@Qualifier("allCountries") 
private Set<String> countries;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@Autowired
@Qualifier("allCountries")
private Set<String> countries;

3.2 Constructor Injection

Once the configuration is built up we just need to inject the beans using the correct qualifier. Let us inject the collection beans using Constructor injection. This is my favorite way of injection. Always favor Constructor injection as compared to setter and field injection.

import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class CollConstructorInjection {

	private final Set<String> countries;
	private final List<String> names;
	private final Map<String, String> symbols;

	@Inject
	public CollConstructorInjection(
			@Named("allCountries") Set<String> countries, 
			@Named("names") List<String> names,
			@Named("specialSymbols") Map<String, String> symbols) {
		this.countries = countries;
		this.names = names;
		this.symbols = symbols;
	}

	public void printCountries() {
		System.out.println(countries);
	}

	public void printNames() {
		System.out.println(names);
	}

	public void printSymbols() {
		System.out.println(symbols);
	}

}

Output : 
[Canada, Netherlands, United States, France]
[Ava, Emma, Benjamin, Bruce]
{#=Octothorpe, $=Dollar, %=Percent, *=Asterisk, ~=Tilde}

3.3 Setter Injection

Do not use setter injection. The example here shows how you can inject collections using setter injection, that’s it. Always favor constructor injection.

import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class CollSetterInjection {

	private Set<String> countries;
	private List<String> names;
	private Map<String, String> symbols;

	@Inject
	@Named("allCountries")
	public void setCountries(Set<String> countries) {
		this.countries = countries;
	}

	@Inject
	@Named("names")
	public void setCountries(List<String> names) {
		this.names = names;
	}

	@Inject
	@Named("specialSymbols")
	public void setSymbols(Map<String, String> symbols) {
		this.symbols = symbols;
	}

	public void printCountries() {
		System.out.println(countries);
	}

	public void printNames() {
		System.out.println(names);
	}

	public void printSymbols() {
		System.out.println(symbols);
	}

}

3.4 Field Injection

Again, do not use field injection. Always favor constructor injection.

import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class CollFieldInjection {

	@Inject
	@Named("allCountries")
	private Set<String> countries;

	@Inject
	@Named("names")
	private List<String> names;

	@Inject
	@Named("specialSymbols")
	private Map<String, String> symbols;

	public void printCountries() {
		System.out.println(countries);
	}

	public void printNames() {
		System.out.println(names);
	}

	public void printSymbols() {
		System.out.println(symbols);
	}
}

3.5 Test

Let us run tests to make sure what we did was correct. We won’t use Mockito. Just plain, simple junit tests with injection.

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({ "com.justamonad" })
public class TestConfig {

}

Writing Tests.

import javax.inject.Inject;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CollInjectionTest {

	@Inject
	private CollConstructorInjection collConstructorInjection;
	
	@Inject
	private CollFieldInjection collFieldInjection;
	
	@Inject
	private CollSetterInjection collSetterInjection;

	@Test
	public void collConstructorInjectionTest() {
		collConstructorInjection.printCountries();
		collConstructorInjection.printNames();
		collConstructorInjection.printSymbols();
	}
	
	@Test
	public void collFieldInjectionTest() {
		collFieldInjection.printCountries();
		collFieldInjection.printNames();
		collFieldInjection.printSymbols();
	}
	
	@Test
	public void collSetterInjectionTest() {
		collSetterInjection.printCountries();
		collSetterInjection.printNames();
		collSetterInjection.printSymbols();
	}

}

That is it on injection Collection in Spring.

Now a very interesting question rises. What if I want to inject multiple beans of the same class in a Collection? Let us see with an example on what this means. 

4. Injecting Custom Bean in Collection

Create a class whose objects you want to inject. Let us call it a BeanClass.

4.1 BeanClass

public class BeanClass {

	private final String str;

	public BeanClass(String str) {
		this.str = str;
	}

	public String getStr() {
		return str;
	}

}

Once we create a class, we can create multiple objects of this class and in the Configuration class. 

4.2 BeanClass Configuration

@Configuration
public class BeanClassConfiguration {

	@Bean
	public BeanClass beanClass1() {
		return new BeanClass("Jon Snow");
	}

	@Bean
	public BeanClass beanClass2() {
		return new BeanClass("Sansa Stark");
	}

	@Bean
	public BeanClass beanClass3() {
		return new BeanClass("Arya Stark");
	}

}

4.3 List Injection

Now we can inject all the Spring managed beans in a List. We use constructor injection to inject all the different objects in a List.

@Named
public class BeanClassInjection {

	private final List<BeanClass> beanClasses;

	@Inject
	public BeanClassInjection(List<BeanClass> beanClasses) {
		this.beanClasses = beanClasses;
	}

	public void callMe() {
		beanClasses.forEach(e -> System.out.println(e.getStr()));
	}

}

4.4. Order

Which injecting in List, if you want to specify the order in which they must be inserted in Collection, use @Order annotation.

@Configuration
public class BeanClassConfiguration {

	@Bean
	@Order(1)
	public BeanClass beanClass1() {
		return new BeanClass("Jon Snow");
	}

	@Bean
	@Order(2)
	public BeanClass beanClass2() {
		return new BeanClass("Sansa Stark");
	}

	@Bean
	@Order(3)
	public BeanClass beanClass3() {
		return new BeanClass("Arya Stark");
	}

}

4.5 Test

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class BeanClassInjectionTest {

	@Inject
	private BeanClassInjection beanClassInjection;
	
	@Test
	public void testMe() {
		beanClassInjection.callMe();
	}
	
}

5. Conclusion

That is all on how to inject Collections in Spring.

Leave a Reply

Your email address will not be published. Required fields are marked *