Предыстория
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 List<Hero> actionHeroes; } @Configuration public class HeroesConfig { @Bean public List<Hero> action() { List<Hero> result = 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 ArrayList<Hero> action() { ArrayList<Hero> result = new ArrayList<>(); result.add(new Terminator()); result.add(new Rambo()); return result; } } @Service @Getter public class ActionHeroesService { @Autowired ArrayList<Hero> actionHeroes; }
Вариант 2. Правильный
Еще один способ связать Spring’у руки заключается в том, что можно попросить его заинжектить в сервис не абы какой List, а bean со специальным именем, добавив Qualifier. Таким образом, нам удастся заинжектить именно наш bean.
@Service @Getter public class ActionHeroesService { @Autowired @Qualifier("action") List<Hero> actionHeroes; }
Injection Map’ы
Если про нюанс injection’а List’а многие знают, то вот с Map’ой дела обстоят как правило хуже.
Давайте напишем сервис, который будет работать с главными героями фильмов. Inject’иться в него будет Map’а, содержащая по ключам названия фильмов, а по значениям bean’ы главных героев:
@Service @Getter public class MainCharactersService { @Autowired Map<String, Hero> mainCharactersByFilmName; } @Configuration public class HeroesConfig { @Bean public Map<String, Hero> mainCharactersByFilmName() { Map<String, Hero> result = 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. Костыльный
Заменяем Map на HashMap:
@Service @Getter public class MainCharactersService { @Autowired HashMap<String, Hero> mainCharactersByFilmName; } @Configuration public class HeroesConfig { @Bean public HashMap<String, Hero> mainCharactersByFilmName() { HashMap<String, Hero> result = new HashMap<>(); result.put("rambo", new Rambo()); result.put("terminator", new Terminator()); result.put("LOTR", new Gandalf()); return result; } }
Вариант 2. Правильный
Добавляем Qualifier:
@Service @Getter public class MainCharactersService { @Autowired @Qualifier("mainCharactersByFilmName") Map<String, Hero> mainCharactersByFilmName; }
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/487750/
Добавить комментарий