Hello, Spring!

Spring Framework — многофункциональный фреймворк для Java, состоящий из нескольких крупных модулей и предоставляющий различные сервисы java разработчикам.

Центральная концепция фреймворка — контейнер, управляющий объектами, и конфигурационный контекст (context), описывающий приложение и дополнительную функциональность.

Подготовка

Вначале создадим проект с помощью maven:

И добавим к нему и JUnit:

Помимо зависимостей, понадобятся ещё два плагина, один для сборки jar файла с зависимостями, другой для запуска интеграционных тестов:

Приложение

Как заведено при изучении нового в программировании, первым делом надо поздороваться с пользователем. Приветствовать пользователя мы будем с использованием целых трёх сервисов: один будет подбрасывать монетку, а два других выбирать,  как и с кем здороваться.

Начнём с монетки:

Монетку надо покрыть юнит-тестами, но протестировать её не так просто, нам придётся написать свою собственую реализацию, дублёра Random, поведением которой мы сможем задавать. Такая реализация называется stub:

На основе броска монетки выберем, кого приветствовать:

Чтобы протестировать этот сервис, нам понадобиться дублёр монетки и именно для этого она была разделена на интерфейс и его реализацию, так как для теста нам нужна другая реализация:

Наконец, напишем сам класс приветствия:

Для теста сервиса приветствия придётся написать тестовую реализацию GreeterTarget:

Проверим что все тесты успешны и перейдём непосредственно к Spring:

Spring

Я сразу, при написании кода, пометил каждый класс аннотацией @Service, объявляющей класс Spring bean. С этой аннотацией жизненным циклом этих классов управляет Spring и нам не надо беспокоиться о их создании. Но чтобы Spring смог создать объекты этих классов, а в классах нет конструкторов по умолчанию, каждый конструктор имеет аннотацию @Inject.

Сочетание этих аннотаций говорит Spring’у: «Возьми класс и создай из него объект. При создании поищи существующие объекты подходящего типа и передай их в конструктор». Самая настоящая инверсия управления: сервисы говорят «Дай-ка мне реализацию», а Spring уже подбирает реализацию из доступных и отдаёт её сервисам при создании. Необходимость в ручном связывании компонентов отпала.

Однако у внимательного читателя возникает вопрос: Greeter зависит от GreeterTarget, который создаст Spring. GreeterTarget зависит от Coin, которую тоже создась Spring. Coin зависит от Random, а кто создаст объект Random? Очевидно, что это должен быть Spring, но как? Это библиотечный класс и к нему нельзя добавить аннотацию @Service. Зато его можно создать вручную, в специальном конфигурационном классе:

Конфигурационный класс, помеченный аннотацией @Configuration, настраивает контекст исполнения Spring, в том числе и добавляя к нему дополнительные Spring beans.

Аннотация  @ComponentScan("ru.easyjava.spring")  говорит Spring’у, что необходимо просканировать классы в пакете ru.easyjava.spring на наличие Spring аннотаций, таких как @Service, и обработать их.

Интеграционный тест

Теперь, когда все компоненты приложения готовы, можно проверить, как они взаимодействуют друг с другом.

Spring включает в себя небольшой инструментарий для упрощения тестирования и, в частности, поддержку загрузки контекста в JUnit тестах. Используя специальный runner  SpringJUnit4ClassRunner, мы инициализируем Spring контест автоматически при запуске теста, а аннотация  @ContextConfiguration указывает, как именно мы хотим сконфигурировать контекст.

Обратите внимание, что класс интеграционного теста имеет суффикс *IT, а не *Test. По соглашению все классы имеющие суффикс *IT признаются maven’ом интеграционными тестами и запускаются отдельно от юнит-тестов:

Сразу видно, что даже такой простой тест исполняется на два порядке медленнее, чем модульный тест. Поэтому их и запускают отдельно.

Приложение

Тесты проходят, давайте запускать приложение:

Разберём класс приложения детально:

Создаёт Spring context используя аннотации и Spring beans из  ContextConfiguration.

Запрашивает из контекста bean типа Greeter. Стоит отметить, что класс к этому времени уже сконструирован, классы, от которых он зависит, тоже уже сконструированы и getBean только возвращает ссылку на существующий экземпляр.

В последней строке мы используем bean Greeter по назначению:

Проверим результат:

Мы видим как запускается IoC контейнер Spring и потом отрабатывает сервис приветствия, выдавая разные приветствия с каждым запуском. Всё получилось 🙂

Код примера доступен на github.