Актуальная версия статьи доступна на моём новом сайте devmark.ru.
Рассмотрим базовые возможности внедрения зависимостей (dependency injection), которые открывает нам Spring.
Создадим обычный maven-проект, где в pom.xml добавим сам Spring и стандартную секцию <build>.
Теперь создадим главный класс TestApp, который будет точкой входа.
Здесь мы создаём контекст на основе конфигурационного файла Spring, который после сборки maven'ом окажется в том же jar-файле. Затем из контекста получаем главный бин MainService и вызываем у него целевой метод.
Поскольку все бины мы будем конфигурировать прямо в коде при помощи аннотаций (секция <annotation-config>), содержимое app-config.xml будет минимальным. Мы лишь указываем базовый пакет (секция <component-scan>), внутри которого Spring автоматически будет искать все бины.
Теперь определим интерфейс нашего главного бина MainService. В связи с особенностями работы Spring, инициализация бина происходит быстрее, если у него есть интерфейс. Да и вообще, с точки зрения ООП всегда хорошо выделять интерфейс. Просто возьмите это за правило.
Его реализация MainServiceImpl, помеченная аннотацией @Service.
Все spring-бины помечаются одной из четырёх аннотаций:
Также обратите внимание на аннотацию @Autowired. Она указывает, что данное поле класса должно быть автоматически инициализировано бином, имеющим такой же интерфейс, как и это поле класса. В нашем примере это некий интерфейс Handler (от англ. "обработчик").
Сам интерфейс Handler предельно прост. В нём только один метод, который возвращает строку.
Предположим, его реализацией может быть бин, возвращающий текущее время в виде строки.
Обратите внимание, что данный бин DateHandler помечен аннотацией@Component.
Хорошей практикой является создание бинов таким образом, чтобы они не имели внутреннего состояния. Например, не создавать поля класса, которые могут менять значения между вызовами методов. Зачем это нужно? Дело в том, что по умолчанию Spring создаёт каждый бин в одном экземпляре, который затем будет передан во все бины, которые в нём нуждаются. И если ваш бин будет хранить состояние, то неизвестно, как он будет себя вести, если его состояние будут одновременно менять различные компоненты.
Теперь, если вы запустите приложение, то увидите на экране текущее время.
Давайте теперь рассмотрим более интересный вариант, когда один бин от другого отличается незначительно. Например, пусть один из них возвращает всё время одну строку, а второй - другую. Создавать ещё два класса было бы излишне. Поэтому сделаем такую реализацию, в которую константное значение будем передавать при инициализации бина. И при этом НЕ будем снабжать её какой-либо аннотацией.
Теперь нам потребуется специальный конфигурационный класс с аннотацией @Configuration, который позволяет более гибко инициализировать бины.
Здесь всё просто: один метод с аннотацией @Bean - один бин. В конструктор мы передаём ему константу. В итоге Spring создаст два бина, один из который всё время будет возвращать "Alice", а другой - "Bob".
Теперь для вызова новых обработчиков нам нужно внедрить их в наш сервис MainServiceImpl. Можно конечно прописать ещё два поля, но и тут Spring облегчает нам задачу. Достаточно создать одно поле, представляющее из себя коллекцию List из интерфейсов Handler, в которую Spring автоматически добавит ВСЕ бины, имеющие этот интерфейс. Вот ещё одна причина, по которой удобно использовать интерфейсы.
Наш сервис примет такой вид:
В итоге в нашей коллекции обработчиков будет ровно три бина (DateHandler и два экземпляра NameHandler).
Для отображения значения каждого из них воспользуемся Stream API из Java 8, что равносильно обычному циклу foreach и вызову метода getString() у каждого из элементов.
Исходники проекта: https://github.com/nordmine/spring-simple-example
Если вам помог данный материал, поставьте +1.
Рассмотрим базовые возможности внедрения зависимостей (dependency injection), которые открывает нам Spring.
Создадим обычный maven-проект, где в pom.xml добавим сам Spring и стандартную секцию <build>.
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.5.RELEASE</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
Теперь создадим главный класс TestApp, который будет точкой входа.
public class TestApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("app-config.xml"); MainService service = context.getBean(MainService.class); service.doWork(); } }
Здесь мы создаём контекст на основе конфигурационного файла Spring, который после сборки maven'ом окажется в том же jar-файле. Затем из контекста получаем главный бин MainService и вызываем у него целевой метод.
Поскольку все бины мы будем конфигурировать прямо в коде при помощи аннотаций (секция <annotation-config>), содержимое app-config.xml будет минимальным. Мы лишь указываем базовый пакет (секция <component-scan>), внутри которого Spring автоматически будет искать все бины.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config /> <context:component-scan base-package="ru.devmark.test"/> </beans>
Теперь определим интерфейс нашего главного бина MainService. В связи с особенностями работы Spring, инициализация бина происходит быстрее, если у него есть интерфейс. Да и вообще, с точки зрения ООП всегда хорошо выделять интерфейс. Просто возьмите это за правило.
public interface MainService { void doWork(); }
Его реализация MainServiceImpl, помеченная аннотацией @Service.
@Service public class MainServiceImpl implements MainService { @Autowired private Handler handler; @Override public void doWork() { System.out.println(handler.getString()); } }
Все spring-бины помечаются одной из четырёх аннотаций:
- Controller - бины, содержащие маппинг входящих http-запросов
- Service - бины, реализующие бизнес-логику приложения
- Repository - бины, работающие с БД
- Component - все остальные бины
Также обратите внимание на аннотацию @Autowired. Она указывает, что данное поле класса должно быть автоматически инициализировано бином, имеющим такой же интерфейс, как и это поле класса. В нашем примере это некий интерфейс Handler (от англ. "обработчик").
Сам интерфейс Handler предельно прост. В нём только один метод, который возвращает строку.
public interface Handler { String getString(); }
Предположим, его реализацией может быть бин, возвращающий текущее время в виде строки.
@Component public class DateHandler implements Handler { @Override public String getString() { return LocalDateTime.now().toString(); } }
Обратите внимание, что данный бин DateHandler помечен аннотацией
Хорошей практикой является создание бинов таким образом, чтобы они не имели внутреннего состояния. Например, не создавать поля класса, которые могут менять значения между вызовами методов. Зачем это нужно? Дело в том, что по умолчанию Spring создаёт каждый бин в одном экземпляре, который затем будет передан во все бины, которые в нём нуждаются. И если ваш бин будет хранить состояние, то неизвестно, как он будет себя вести, если его состояние будут одновременно менять различные компоненты.
Теперь, если вы запустите приложение, то увидите на экране текущее время.
Давайте теперь рассмотрим более интересный вариант, когда один бин от другого отличается незначительно. Например, пусть один из них возвращает всё время одну строку, а второй - другую. Создавать ещё два класса было бы излишне. Поэтому сделаем такую реализацию, в которую константное значение будем передавать при инициализации бина. И при этом НЕ будем снабжать её какой-либо аннотацией.
public class NameHandler implements Handler { private final String name; public NameHandler(String name) { this.name = name; } @Override public String getString() { return name; } }
Теперь нам потребуется специальный конфигурационный класс с аннотацией @Configuration, который позволяет более гибко инициализировать бины.
@Configuration public class AppConfiguration { @Bean public Handler firstNameHandler() { return new NameHandler("Alice"); } @Bean public Handler secondNameHandler() { return new NameHandler("Bob"); } }
Здесь всё просто: один метод с аннотацией @Bean - один бин. В конструктор мы передаём ему константу. В итоге Spring создаст два бина, один из который всё время будет возвращать "Alice", а другой - "Bob".
Теперь для вызова новых обработчиков нам нужно внедрить их в наш сервис MainServiceImpl. Можно конечно прописать ещё два поля, но и тут Spring облегчает нам задачу. Достаточно создать одно поле, представляющее из себя коллекцию List из интерфейсов Handler, в которую Spring автоматически добавит ВСЕ бины, имеющие этот интерфейс. Вот ещё одна причина, по которой удобно использовать интерфейсы.
Наш сервис примет такой вид:
@Service public class MainServiceImpl implements MainService { @Autowired private List<Handler> handlers; @Override public void doWork() { handlers.forEach(h -> System.out.println(h.getString())); } }
В итоге в нашей коллекции обработчиков будет ровно три бина (DateHandler и два экземпляра NameHandler).
Для отображения значения каждого из них воспользуемся Stream API из Java 8, что равносильно обычному циклу foreach и вызову метода getString() у каждого из элементов.
Исходники проекта: https://github.com/nordmine/spring-simple-example
Если вам помог данный материал, поставьте +1.
Комментариев нет:
Отправить комментарий