- Inject Collection of Beans in Spring
- Overview
- Setup Multiple Beans of Same Type
- Inject Bean References as List
- Inject Bean References as Set
- Inject Bean References as Map
- Sort and Inject Beans References as List
- Summary
- Spring: Injecting an Array or List of some type into a bean
- 3 Answers 3
- Некоторые тонкости injection’а коллекций в Spring’е
- Предыстория
- Сценарий
- Injection List’а
- Вариант 1. Костыльный
- Вариант 2. Более правильный
- Injection Map’ы
- Вариант 1. Костыльный
- Вариант 2. Более правильный
Inject Collection of Beans in Spring
An example oriented tutorial on auto wiring and injecting references of multiple beans of a same type as a list, set or a map in Spring Framework.
Overview
Spring auto wiring is a powerful framework for injecting dependencies. It provides a flexible and dynamic way of declaring and auto wiring dependencies by different ways. Sometimes, we may want to find all implementations of a super class or super interface and perform a common action on them. To do that, we need to hold references to such Spring Beans in some sort of collection. Spring framework provides a painless and more readable way to collect all the beans that belong to a same type.
In this tutorial we will learn to Inject references of multiple beans into a collection and to sort the bean references when they are injected.
Setup Multiple Beans of Same Type
Before we begin with Injecting Bean References as List, Set, or Map we will first create an abstract class and a couple sub classes. Remember, instead of an abstract class we can also create an interface and a multiple implementations and spring will work in the same way.
Next is a super class which will also serve as a Type in our example.
public abstract class StorageService Code language: Java (java)
@Component public class FileSystemStorageService extends StorageService Code language: Java (java)
@Component public class DatabaseStorageService extends StorageService Code language: Java (java)
@Component public class CloudStorageService extends StorageService Code language: Java (java)
In order for Spring to be able to instantiate and manage various classes, we need to make them Spring Beans. Thus, we have marked each of our subclasses with @Component annotation.
Inject Bean References as List
We will use the three beans to show how to inject Beans of same type as a List. To do that we need to define an instance variable, which is a list of super class or super interface type.
private final List storeServices;
Code language: Java (java)
The smart auto wiring capabilities in Spring, helps it to understand the intended type. Thus, spring will scan all the beans that belong to the given type, or in other words beans that extend StorageService and prepares a list.
Let’s see a complete example of auto wiring beans as a list.
@Component public class FileProcessor < private final List storeServices; @Autowired public FileProcessor(List storeServices) < this.storeServices = storeServices; > @PostConstruct public void listImplementations() < storeServices .forEach(storageService ->System.out.println(storageService.getClass().getSimpleName())); > >
Code language: Java (java)
This class has an instance variable, which is a List of StorageService type. Next, we are using Constructor dependency injection by using @Autowired annotation on the constructor. After that, we are printing the simple names of each of the elements of the list.
Remember, spring can inject collection of beans of a same type by using any type of injection method we use. Thus, we can also use Field dependency injection, or a Setter dependency Injection.
When we start the application, we see the list is correctly populated with references to all three implementations of the superclass StorageService.
CloudStorageService DatabaseStorageService FileSystemStorageService
Inject Bean References as Set
Similarly, we can inject a Set of beans that belong to a same type.
private final Set storeServices; @Autowired public FileProcessor(Set storeServices) < this.storeServices = storeServices; >
Code language: Java (java)
Inject Bean References as Map
Interestingly we can also inject beans of same type as a Map. All we need to do is define an instance variable of type Map.
private final Map storeServices;
Code language: Java (java)
Spring has an excellent type inference system that helps it to recognise intended type of the dependency being injected. When Spring sees the auto wired map having StorageService as the value it prepares a map where key is the bean id. The Bean Id can be specified on each bean. However, if it is not specified Spring will derive it from the name of the bean.
Next is a complete example of injecting Bean references as a Map.
@Component public class FileProcessor < private final Map storeServices; @Autowired public FileProcessor(Map storeServices) < this.storeServices = storeServices; > @PostConstruct public void listImplementations() < storeServices .forEach((id, storageService) ->System.out.println(id + ": " + storageService.getClass().getSimpleName())); > >
Code language: Java (java)
Note that in the post construct method we are printing each entry from the map on console. The output will look like this:
cloudStorageService: CloudStorageService databaseStorageService: DatabaseStorageService fileSystemStorageService: FileSystemStorageService
Spring has intelligently populated the Map of bean Id and the bean references correctly.
Sort and Inject Beans References as List
We can also provide a custom sort order for the auto wired List of Bean References. To do that, we can use @Order annotation on the beans to specify their index position.
For example, next snippet shows all three beans with specified sort orders.
@Component @Order(2) public class CloudStorageService extends StorageService < >// . @Order(2) @Component public class DatabaseStorageService extends StorageService < >// . @Order(1) @Service("storeService") public class FileSystemStorageService extends StorageService Code language: Java (java)
When we run the application, we see the output is now in the given order.
FileSystemStorageService CloudStorageService DatabaseStorageService
Summary
In this tutorial, we understood that Spring Framework helps us collect multiple beans of a same type and inject them into another bean in the form of collections. We created an abstract class and three subclasses, of which all three sub classes were Spring Beans.
Then we created example of injecting multiple beans into a List, into a Set, and lastly into a Map. Also, we learned that when we auto wire multiple beans of same type as a Map, the key is the Id of the bean. Finally, we learned that we can specify the order for beans while injecting them into Collections.
For more on Spring and Spring Boot, please visit Spring Tutorials.
Spring: Injecting an Array or List of some type into a bean
If I have an interface I , and some classes that implement it, is it possible to inject an array I[] or List into a bean? I see that it can be done for List using , but I would like to parametrize my list here — either that or get an array of type I . The number of elements in the list/array is fixed and determined before runtime. Thanks for any feedback in advance 🙂
3 Answers 3
I Spring 3.1 it is possible to inject it as:
where I is your interface (but it should be concrete).
Or you could use Spring Java Config ( @Configuration ) to produce ( @Bean ) named lists and inject them using Qualifier or @Named .
Also you may define typed named list as here:
Thats impossible to achieve because of JAVA type erasure on compile time. The JAVA generics are only available at compile time and are there to ensure type safety. In runtime there are only Object’s(references) left.
The only thing you can do to assure type safety (but still runtime) is to have an array of any type and use spring or tags to populate the data. Then at runtime you will get an exception when you try to populate a Integer[] with Strings.
If you use generics you can have Set and in run time end up with Set because of the mentioned type erasure.
This post suggests otherwise . stackoverflow.com/a/2416145/1154145. I’m not sure if this would work for interfaces however.
Некоторые тонкости injection’а коллекций в Spring’е
Всем привет! Меня зовут Владислав Родин. В настоящее время я преподаю на портале OTUS курсы, посвященные архитектуре ПО и архитектуре ПО, подверженного высокой нагрузке. Сейчас в OTUS’е открыт набор на новый поток курса Разработчик на Spring Framework. В преддверии старта курса я решил написать небольшой авторский материал, которым хочу поделиться с вами.
Предыстория
Spring содержит внутри себя много «магии», осуществляя самостоятельно некоторые неочевидные вещи. Незнание или непонимание этого может приводить к side-эффектам, с которыми вы можете столкнуться в процессе написания своего приложения, используя данный framework.
Одной из таких неочевидных вещей является injection интерфейсов Java Collection Framework’а. Самостоятельно наступив на грабли, связанные с этой темой, и услышав очередные вопросы от коллег, я решил с ней разобраться и зафиксировать результаты своих исследований в виде статьи с надеждой, что она кому-то поможет уже в работе или при начальном освоении Spring’а.
Сценарий
Давайте рассмотрим сервис, который будет работать с киногероями. Их будет 3: Рембо, Терминатор и Гендальф. Каждый их них будет представлять отдельный класс, а родитель у них будет общий- Hero.
public class Hero < >@Component public class Rambo extends Hero < @Override public String toString() < return "Rambo"; >> @Component public class Terminator extends Hero < @Override public String toString() < return "Terminator"; >> @Component public class Gandalf extends Hero < @Override public String toString() < return "Gandalf"; >>
Injection List’а
Давайте предположим, что мы хотим создать сервис, который будет работать с героями боевиков. Наверное, придется в него заинжектить list таких героев.
Не проблема! Создаем сервис и конфигурацию:
@Service @Getter public class ActionHeroesService < @Autowired ListactionHeroes; > @Configuration public class HeroesConfig < @Bean public Listaction() < Listresult = new ArrayList<>(); result.add(new Terminator()); result.add(new Rambo()); return result; > >
Все прекрасно, однако при проверке можно обнаружить, что в список попал и Гендальф!
Spring, увидев, что надо заинжектить List, обошел bean’ы, расположенные в context’е, нашел среди них все, подходящие под generic type, собрал из них List и заинжектил его, проигнорировав наш List.
Как заставить Spring сделать то, что мы хотим?
Вариант 1. Костыльный
Поскольку проблема именно в попытке заинжектить интерфейс Java Collection Framework’а, можно просто заменить List на ArrayList в сервисе и, конечно же, в конфигурации, чтобы Spring нашел экземпляр нужного нам класса. Тогда все будет работать так, как мы ожидали.
@Configuration public class HeroesConfig < @Bean public ArrayListaction() < ArrayListresult = new ArrayList<>(); result.add(new Terminator()); result.add(new Rambo()); return result; > > @Service @Getter public class ActionHeroesService < @Autowired ArrayListactionHeroes; >
Вариант 2. Более правильный
Еще один способ связать Spring’у руки заключается в том, что можно попросить его заинжектить в сервис не абы какой List, а bean со специальным именем, добавив Qualifier. Таким образом, нам удастся заинжектить именно наш bean.
@Service @Getter public class ActionHeroesService < @Autowired @Qualifier("action") ListactionHeroes; >
Injection Map’ы
Если про нюанс injection’а List’а многие знают, то вот с Map’ой дела обстоят как правило хуже.
Давайте напишем сервис, который будет работать с главными героями фильмов. Inject’иться в него будет Map’а, содержащая по ключам названия фильмов, а по значениям bean’ы главных героев:
@Service @Getter public class MainCharactersService < @Autowired MapmainCharactersByFilmName; > @Configuration public class HeroesConfig < @Bean public MapmainCharactersByFilmName() < Mapresult = new HashMap<>(); result.put("rambo", new Rambo()); result.put("terminator", new Terminator()); result.put("LOTR", new Gandalf()); return result; > >
При запуске же можно увидеть, что ключом Гендальфа является не LOTR, а gandalf, из чего можно сделать вывод, что записалось не название фильма, а имя bean’а, тогда как в случае с Рембо и терминатором просто повезло: имена главных героев совпадают с названиями фильмов.
На самом деле, когда необходимо заинжектить Map’у, ключом которой является String, а значением bean’ы, Spring (как и в случае с List’ом) просто проигнорирует предложенную нами Map’у, пройдется по контексту и соберет все подходящие bean’ы, и создаст Map’у с ними в качестве значений и с их именами в качестве ключей.
Варианты обхода похожи на те, которые работали для List’а:
Вариант 1. Костыльный
@Service @Getter public class MainCharactersService < @Autowired HashMapmainCharactersByFilmName; > @Configuration public class HeroesConfig < @Bean public HashMapmainCharactersByFilmName() < HashMapresult = new HashMap<>(); result.put("rambo", new Rambo()); result.put("terminator", new Terminator()); result.put("LOTR", new Gandalf()); return result; > >
Вариант 2. Более правильный
@Service @Getter public class MainCharactersService < @Autowired @Qualifier("mainCharactersByFilmName") MapmainCharactersByFilmName; >