Инициализация Spring: XML, аннотации, Java конфигурация и … Groovy

В примере Hello, Spring! контекст создавался с использованием аннотаций, таких как @Service, и специального класса, описывающего конфигурацию контекста , смешивая два разных подхода к конфигурирования контекста.

В настоящий момент Spring framework поддерживает четыре разных способа конфигурирования контекста, каждый из которых заслуживает отдельного рассмотрения.

Подготовка

Я создал многомодульный maven проект, с различными подмодулями для каждого варианта конфигурирования контекста. За основу каждого модуля я взял код примера Hello, Spring! и изменил его соответствующим образом.

 

Исторически конфигурирование контекста c использованием XML было первым методом конфигурирования, появившемся в Spring.

Конфигурирование с помощью XML заключается в создании xml файла (традиционно носящего названия вида «context.xml», «applicationContext.xml» и т.д.), описывающего Spring beans, процесс их создания и взаимосвязи между ними. Поэтому в первую очередь убедимся, что в коде отсутствуют аннотации @Service  и @Inject и сразу после их удаления напишем им замену в xml:

Файл «/resource/applicationContext.xml» заменяет и класс ContextConfiguration и аннотации.

Вначале мы определили бин «coin», передав ему в конструктор внутренний анонимный бин, созданный из java.util.Random. Связывание было проведено по типу аргумента, а передаваемый в конструктор бин был определён на месте.

Бин «coin» используется в следующем определении. GreeterTargetImpl требует, чтобы ему передали экземпляр Coin и мы его передаём. Причём в этот раз передаём его как параметр номер 0 и, вместо создания бина на месте, мы ссылаемся на ранее определённый бин с именем «coin».

Бин «greeter» демонстрирует третий метод передачи параметров — по имени переменной.

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

Последним действием надо будет исправить код интеграционного теста и само приложение. В тесте заменим @ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes = ru.easyjava.spring.ContextConfiguration.class) на  @ContextConfiguration("/applicationContext.xml").

В приложении заменим создание контекста на ClassPathXmlApplicationContext("/applicationContext.xml") и можно проверять результат:

Использованием XML конфигурации конечно неудобно тем, что приходится кроме написания непосредственно кода описывать ещё и его использование в Spring, при этом не имея возможности проверить ошибки в конфигурации до запуска тестов (или даже приложения). Кроме того, в декларативном xml файле достаточно сложно реализовать конфигурацию, требующую активных действий во время создания контекста.

С другой стороны, xml конфигурация представляет собой централизованное описание приложения, которое может хранится отдельно от кода, позволяя менять структуру приложения без пересборки. С помощью xml  можно использовать в качестве Spring beans сторонний код. И, что для кого-то может оказаться важным, в коде отсутствуют специфичные для Spring вещи.

Annotations

Конфигурирование Spring context с помощью аннотаций уже рассматривалось в Hello, Spring!, но полезно будет попробовать аннотации в чистом виде.

К коду оригинального примера добавится ещё один класс:

Который нужен только лишь для того, чтобы «навесить» аннотацию @Service  на java.util.Random. В коде самого приложения аргумент AnnotationConfigApplicationContext меняется на название package, в котором искать бины:

Код интеграционного теста придётся сильно изменить. Дело в том, что в настоящий момент @ConfigurationContext не поддерживает annotation-only конфигурацию, поэтому приходится использовать костыль:

Конечно, использование аннотаций для определения бинов и их зависимостей, весьма удобно и упрощает разработку, но недостатков у этого подхода больше всего. Конфигурация контекста получается децентрализованной, так что неосторожное добавление нового бина может внезапно изменить работу всего приложения (и это, опять таки, не узнать до запуска тестов/приложения). Так же в код попадают Spring специфичные вещи, которые потом могут затруднить смену платформы. С аннотациями использование стороннего кода либо невозможно, либо требует определённых подпорок и костылей. Кроме того, единственной возможностью изменить поведение приложения будет его пересборка.

Java configuration

Третий подход — программное создание бинов, реализующий модный принцип convention over configuration, соглашения по конфигурации. Стоит отметить, что под программным созданием бинов я понимаю создание бинов на стадии формирования контекста, а не после того, как приложение уже запущено.

Так же, как и в случае с xml подходом, начнём с удаления  аннотаций  @Service  и @Inject. Все бины будут теперь определяться в ContextConfiguration:

Spring вызывает в конфигурационных классах методы с аннотацией @Bean. Объекты, возвращённые этими методами, регистрируются как Spring бины. Названия бинов соответствуют названиям методов, которые их порождают.

Вызов методов, создающих бины, вручную, вполне безопасен, потому что Spring изменяет код создания бина, чтобы пытаться вернуть уже существующий подходящий бин и только если это невозможно, вызывать создающий код. Поэтому в методе coin()  не создаётся второй экземпляр бина random, а используется ранее созданый бин. По этой причине методы, имеющие аннотацию @Bean, не должны объявляться final.

Важно и название класса конфигурации: несмотря на искушение назвать конфигурационный класс Context, делать этого не следует. Дело в том, что Spring создаст бин и из этого класса, а в качестве имени использует имя класса. А имя «Context» уже занято самим Spring.

Java конфигурация выглядит наилучшим образом, если сравнивать её достоинства и недостатки. Это и централизованность как в xml; и безопасность типов; и простой рефакторинг; и отсутствия spring специфичных вещей в коде; и возможность выполнения каких-либо действий на этапе конфигурации. К недостаткам, пожалуй, относится необходимость ручного создания бинов и необходимость пересборки для переконфигурации приложения.

Поддержка Groovy скриптов и специального beans DSL появилась в четвёртой версии Spring как попытка сделать конфигурацию вообще без недостатков. Groovy конфигурация должна объединить в себе достоинства XML конфигурации и Java конфигурации, оставаясь дружественной к аннотациям.

Чтобы попробовать groovy конфигурацию, необходимо добавить groovy runtime в приложение:

Класс ContextConfiguration  заменим скриптом «/resources/applicationContext.groovy»:

Минимальные изменения внесём в классы приложения:

и интеграционного теста:

И проверим, что оно всё ещё работает:

К достоинствам groovy конфигурации можно отнести всё хорошее, что есть в XML и Java подходах: централизованное описание приложения, которое может хранится отдельно от кода, позволяя менять структуру приложения без пересборки (а в перспективе без перезапуска); использование стороннего кода в качестве Spring beans; возможность сохранить код чистым от Spring аннотаций; безопасность типов и простота рефакторинга; выполнение кода на этапе конфигурирования. Однако поддержка groovy конфигурации ещё недостаточно доработана и не все возможности, достижимые с помощью xml/java, доступны с groovy.

Какой подход выбрать?

Используйте все сразу, они совместимы. Аннотации уменьшают накладные расходы на конфигурацию и не заставляют разработчика переключаться между написанием кода и конфигурацией. Java конфигурация хороша для стороннего кода или какой-либо сложной инициализации. XML позволяет вынести конфигурацию контекста за пределы приложения, делая возможным настройку контекста на этапе эксплуатации. Groovy заменит и java и xml, но пока он не достаточно хорош.

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