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.