14 января 2018
Тэги: Java, Java 8, maven, Spring, Stream API, ООП.
Рассмотрим базовые возможности dependency injection (внедрения зависимостей), которые открывает нам Spring.
Создадим обычный maven-проект, где в pom.xml добавим сам Spring (артефакт spring-context) и секцию build со стандартным плагином maven-compiler-plugin, в котором указываем версию java в source и target.
Теперь создадим главный класс TestApp, который будет точкой запуска приложения.
Здесь мы создаём контекст на основе конфигурационного файла Spring, который после сборки maven'ом окажется внутри jar-файла. В данном случае мы используем ClassPathXmlApplicationContext, но есть и много других реализаций. Затем из контекста получаем главный бин MainService и вызываем у него целевой метод.
Поскольку все бины мы будем конфигурировать прямо в коде при помощи аннотаций (секция annotation-config), содержимое app-config.xml будет минимальным. Мы лишь указываем базовый пакет (секция component-scan), внутри которого Spring автоматически будет искать все бины.
Теперь определим интерфейс нашего главного бина MainService. В связи с особенностями работы Spring, инициализация бина происходит быстрее, если у него есть интерфейс. Да и вообще, с точки зрения ООП всегда хорошо выделять интерфейс. Просто возьмите это за правило.
Его реализация MainServiceImpl, помеченная аннотацией @Service.
Все spring-бины помечаются одной из четырёх аннотаций:
Component – наиболее обобщённая аннотация и три другие являются её частными случаями. По большому счёту, разницы между ними нет и почти любой бин будет работать с любой аннотацией, но с точки зрения логики приложения лучше придерживаться такого деления.
Также обратите внимание на аннотацию @Autowired. Она указывает, что в данный конструктор в качестве аргумента нужно поместить бин с подходящим интерфейсом. В нашем примере это некий интерфейс Handler.
Сам интерфейс Handler предельно прост. В нём только один метод, который возвращает строку.
Предположим, одной из его реализаций может быть бин, возвращающий текущее время в виде строки.
Обратите внимание, что данный бин DateHandler помечен аннотацией @Component.
Хорошей практикой является создание бинов таким образом, чтобы они не имели внутреннего состояния. Например, не создавать поля класса, которые могут менять значения между вызовами методов. Зачем это нужно? Дело в том, что по умолчанию Spring создаёт каждый бин в одном экземпляре, который затем будет передан во все бины, которые в нём нуждаются. И если ваш бин будет хранить состояние, то неизвестно, как он будет себя вести, если его состояние будут одновременно менять различные компоненты.
Теперь, если вы запустите приложение, то увидите на экране текущее время.
Давайте теперь рассмотрим более интересный вариант, когда один бин от другого отличается незначительно. Например, пусть один из них возвращает всё время одну строку, а второй – другую. Создавать ещё два класса было бы излишне. Поэтому сделаем такую реализацию, в которую константное значение будем передавать при инициализации бина. И при этом НЕ будем снабжать её какой-либо аннотацией.
Теперь нам потребуется специальный конфигурационный класс с аннотацией @Configuration, который позволяет более гибко инициализировать бины.
Здесь всё просто: один метод с аннотацией @Bean – один бин. В конструктор мы передаём ему константу. В итоге Spring создаст два бина, один из который всё время будет возвращать «Alice», а другой – «Bob».
Теперь для вызова новых обработчиков нам нужно внедрить их в наш сервис MainServiceImpl. Можно конечно прописать ещё два поля, но и тут Spring облегчает нам задачу. Достаточно создать одно поле, представляющее из себя коллекцию List из интерфейсов Handler, в которую Spring автоматически добавит ВСЕ бины, имеющие этот интерфейс. Вот ещё одна причина, по которой удобно использовать интерфейсы.
Наш сервис примет такой вид:
В итоге в нашей коллекции обработчиков будет ровно три бина (DateHandler и два экземпляра NameHandler).
Для отображения значения каждого из них воспользуемся Stream API из Java 8, что равносильно обычному циклу foreach и вызову метода getString() у каждого из элементов.
Kotlin, Java, Java 11, Java 8, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.