
Что вообще такое starter?
Допустим, Вы разрабатываете несколько приложений или микросервисов на Java. Каждое из них уникальное, и содержит свою собственную бизнес логику. Однако, в каждом из них может быть необходимость использовать общую логику. Например, логику аутентификации, как это часто бывает в мире микросервисов.
Есть несколько способов реализовать общую логику в нескольких приложениях:
-
можно реализовать её в каждом из приложений
-
можно вынести её в отдельный модуль, который при сборке будет автоматически включён в качестве части приложения
-
можно вынести её в отдельную библиотеку, и подключать её как отдельную зависимость
Первый подход плох тем, что на каждое изменение в общей логике, нужно эти изменения скопировать в каждое приложение, в котором эта самая общая логика используется. Это рабочий, но плохоуправляемый и трудноподдерживаемый вариант для коммерческой разработки.
Второй подход часто применяется в случае, когда разработка систем ведётся в монорепозиториях. С одной стороны, это означает, что изменив общую логику, разработчику необходимо изменить и все места в системе (все микросервисы и приложения, использующие общую логику). С другой стороны, без знания контекста использования общей логики в разных местах — вносить изменения рискованно.
Третий подход — это подход, при котором общая логика выпускается в виде отдельной подключаемой библиотеки, со своим собственным версионированием и релизным циклом. Это самый гибкий подход, поскольку позволяет разработчикам каждого приложения или микросервиса решать вопрос об обновлении зависимости с общей логикой самостоятельно.
Так что же такое — starter’ы в Spring Boot? Это и есть отдельные библиотеки, со своим релизным циклом, которые позволяют использовать фишки самого Spring’а. Конкретно, в них могут содержаться заранее сконфигурированные Spring бины, которые решают какую-либо задачу (например, интеграция с чем-либо).
Иными словами — каждый стартер представляет собой “строительный блок” с кодом и собственной конфигурацией, которая, в последствие, может быть изменена уже в приложении.
Примеры starter’ов
Возможно, Вы и не задумывались, но каждый раз, когда Вы пользуетесь Spring Initializr’ом, добавляя зависимости в проект, Вы добавляете starter’ы. Например:
-
spring-boot-starter-web— для работы с Spring Web и MVC -
spring-boot-starter-security— для организации защищённого доступа к ресурсам сервиса -
или
spring-boot-starter-test— для возможности написания тестов с подъёмом Spring контейнера
Как создать свой starter?
Это не сложно.
Давайте разберём на примере — создадим стартер для получения информации по погоде в выбранной локации.
Полный код проектов стартера и клиентского приложения доступен на GitHub
1. Создадим новый Maven проект
Для этого воспользуемся прелестями автогенерации Maven:
mvn archetype:generate
2. Добавим необходимые зависимости в pom.xml
Следующие строки нужно вставить в dependencies секцию pom.xml:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>3.2.4</version> <optional>true</optional> </dependency>
Зависимость объявлена в качестве опциональной, поскольку действительно используемые зависимости будут добавлены самим проектом, в котором и будет использован стартер.
Любые стартеры, как и обычные java-библиотеки, могут иметь собственные зависимости. Добавим ещё одну зависимость, которая позволит нам запрашивать погодные данные из сервиса OpenWeatherMap.
<dependency> <groupId>com.github.prominence</groupId> <artifactId>openweathermap-api</artifactId> <version>2.3.0</version> </dependency>
3. Добавим сервисы
Чтобы стартер был полезен, необходимо реализовать сервисы, с нужными нам контрактами. В нашем случае, мы хотим получать информацию по текущей погоде для заранее определённой локации. Для этого нам нужно запросить эту информацию с OpenWeatherMap и вернуть её вызывающему приложению.
Для этих целей добавим в стартер следующий класс:
// Именно этот класс мы будем использовать в клиентском приложении для получения информации по погоде public class WeatherService { /** * Название города, для которого мы будем получать информацию по погоде. */ private final String defaultCity; /* * OpenWeatherMap клиент из библиотеки openweathermap-api. */ private final OpenWeatherMapClient client; public WeatherService(String defaultCity, OpenWeatherMapClient client) { this.defaultCity = defaultCity; this.client = client; } /* * Получить текстовое описание погоды для выбранного города. * @return строка с описанием текущей погоды */ public String getTemperature() { CurrentWeatherRequester requester = client.currentWeather(); SingleResultCurrentWeatherRequestTerminator terminator = requester.single() .byCityName(defaultCity) .unitSystem(UnitSystem.METRIC) // позволяет получать замеры в Цельсиях .retrieve(); Temperature temperature = terminator.asJava().getTemperature(); return temperature.toString(); } }
После действий выше, в нашем стартере будет сервис, который может получать погодные данные для указанного города.
Для нормальной работы WeatherService потребуется откуда-то взять название города, да и OpenWeatherMapClient без SDK-ключа не получить. Поэтому, далее займёмся настройками.
4. Добавим конфигурацию
Добавим default.yaml файл (название может быть любое) в директорию resources:
openweathermap-starter: # SDK-ключ для OpenWeatherMap sdk-key: bd5e378503939ddaee76f12ad7a97608 # Город, для которого стартер будет поставлять погодные данные city: Moscow
Ключ SDK любезно предоставлен Интернетом. На момент написания статьи — работает. Собственный SDK-ключ можно получить на сайте OpenWeatherMap.
Свяжем нашу конфигурацию с кодом с помощью нового класса — OpenWeatherMapProperties:
@ConfigurationProperties(prefix = "openweathermap-starter") public class OpenWeatherMapProperties { private String sdkKey; private String city; public String getSdkKey() { return sdkKey; } public void setSdkKey(String sdkKey) { this.sdkKey = sdkKey; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } }
Затем, необходимо добавить класс авто-конфигурации в наш стартер. Класс авто-конфигурации используется Spring’ом на старте приложения, для того, чтобы проинициализировать имеющиеся в стартере бины и зарезолвить их зависимости. Создадим класс OpenWeatherMapAutoConfiguration:
@Configuration @EnableConfigurationProperties(OpenWeatherMapProperties.class) public class OpenWeatherMapAutoConfiguration { @Bean @ConditionalOnMissingBean public WeatherService weatherService(OpenWeatherMapProperties properties) { return new WeatherService(properties.getCity(), new OpenWeatherMapClient(properties.getSdkKey())); } }
Класс авто-конфигурации необходимо прописать в специальном файле. Для этого необходимо в директории resourcesсоздать папку META-INF, в ней создать ещё одну — spring, и уже в ней разместить файл под названием org.springframework.boot.autoconfigure.AutoConfiguration.imports со следующим содержимым:
ru.fullstackguy.config.OpenWeatherMapAutoConfiguration
Уже на данном этапе, мы можем выполнить сборку стартера, установить его в клиентское приложение и, задав конфигурацию в application.yaml приложения, начать получать погодную информацию.
Однако, чтобы дефолтные настройки стартера из default.yaml использовались в приложении, потребуется выполнить ещё два действия.
Чтобы Spring знал о дефолтных настройках стартера, необходимо их зачитать. Для этого нужно добавить ещё один класс в проект стартера — EnvPostProcessor:
public class EnvPostProcessor implements EnvironmentPostProcessor { private final YamlPropertySourceLoader propertySourceLoader; public EnvPostProcessor() { propertySourceLoader = new YamlPropertySourceLoader(); // Yaml..Loader зачитает для нас конфигурацию из default.yaml } @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { var resource = new ClassPathResource("default.yaml"); // определяем default.yaml как локальный ресурс PropertySource<?> propertySource = null; try { // и просим Yaml...Loader зачитать настройки из файла propertySource = propertySourceLoader.load("openweathermap-starter", resource).get(0); } catch (IOException e) { throw new RuntimeException(e); } // прочитанные настройки проставляются в настройки окружения Spring'а environment.getPropertySources().addLast(propertySource); } }
Обратите внимание, класс EnvPostProcessor не отмечен никакими специальными аннотациями. Именно по этой причине, чтобы Spring о нём узнал, нам понадобиться добавить файл spring.factories в директорию META-INF со следующим содержимым:
org.springframework.boot.env.EnvironmentPostProcessor=ru.fullstackguy.config.EnvPostProcessor
После всех этих действий содержимое папки resources выглядит вот так:
% tree src/main/resources src/main/resources ├── META-INF │ ├── spring │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ └── spring.factories └── default.yaml
Собираем стартер
Вот, собственно, и всё.
Что мы имеем в данный момент? Мы имеем стартер, с собственной конфигурацией, которая будет использована при добавлении стартера в Spring проект.
Чтобы проверить, что всё работает, давайте соберём получившийся проект командой:
mvn clean install
После сборки проекта, наш стартер будет добавлен в локальный maven репозиторий и таким образом, он станет доступен для использования в локальной разработке других проектов.
Использование starter’а
Для проверки работоспособности получившегося стартера, создадим новый Spring Boot проект с помощью Spring Initializr.
Добавим зависимость на наш стартер, добавив следующие строки в блок <dependencies>:
<dependency> <groupId>ru.fullstackguy</groupId> <artifactId>openweathermap-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
После этих действий, мы можем использовать сервисные классы стартера в проекте клиентского приложения:
@Autowired private final WeatherService weatherService; ... var temperature = weatherService.getTemperature();
По умолчанию, стартер будет использовать собственные настройки из default.yaml. Однако, если нам необходимо перенастроить стартер, мы вольны переопределить настройки прямо в application.yaml клиентского приложения. Например, вот пример application.yaml файла из моего клиентского приложения:
spring: application: name: openweathermap-app openweathermap-starter: city: Murmansk
После добавления этой конфиуграции, клиентское приложение будет запрашивать погоду Мурманска, а не Москвы.
Заключение
В данной статье мы разобрали самый простой сценарий создания собственного Spring Boot стартера. Как Вы могли заметить, процесс достаточно не сложный. Самое главное не забывать о том, что каждый starter стоит описывать в собственном пространстве — как пакетном (например, ru.fullstackguy.openweathermapstarter), так и конфигурационном: openweathermap-starter. Это поможет избежать проблем, связанных с пересечением сервисов и конфигураций между несколькими стартерами.
Список материалов
-
Сайт Spring Initlzr — на котором можно сгенерировать Spring проекты, а так же, добавить туда имеющиеся starter’ы Spring’а в удобном пользовательском интерфейсе
-
Хороший список имеющихся Spring starter’ов — список не полный, но подробный
Видео-версия этой статьи
Оригинал этой статьи, а также многие другие на разные темы об IT и разработке, можно найти на моём сайте #fullstackguy.
ссылка на оригинал статьи https://habr.com/ru/articles/806217/
Добавить комментарий