Создание приложения, которое управляется свойствами, с помощью Spring Boot.
Этот пост считается второй частью моего предыдущего поста о некоторых очень часто используемых аннотациях во фреймворке Spring. В этом посте я хотел специально сосредоточиться на том, как мы можем создавать серверные службы с помощью Spring / Spring Boot и управлять определенными разделами приложения с помощью свойств, которые также могут быть предоставлены извне кодовой базы. Когда я говорю о свойствах, на очень высоком уровне я имею в виду способность изменять поведение приложения, просто используя свойства. Например, если бы мы создали серверную службу, которая позволяла бы выполнять вход в систему с использованием сторонних служб (например, вход в систему с использованием службы X или службы Y), и в любой момент времени была бы доступна только одна из них и мы хотим иметь возможность переключаться между двумя вариантами, используя свойства вне приложения.
С таким требованием становится ясно, что нам нужен способ извне приложения, чтобы диктовать приложению, какой вход в систему нужно включить. Мы достигаем этого, используя поддержку Spring для свойств (которые могут быть .properties
или .yaml
files). Эти свойства помещаются в каталог src/main/resources
и начинаются со слова application, за которым следует дефис и имя профиля (если вы хотите узнать о нем больше, обратитесь к документации Spring по профилям).
Для этого поста я использую следующие версии инструментов и фреймворков:
- Весенняя загрузка: 2.4.1
- Котлин: 1.4.21
- JDK: 15
- Gradle: 6.7.1
Без лишних слов, вот как мы создаем приложение Spring Boot Kotlin, которое может условно переключаться между службами с использованием внешних свойств:
Шаг 1. Придумайте ключ и значение настраиваемого свойства
Первое, что мы делаем, - это придумываем имя настраиваемого свойства, чтобы указать службу входа в систему, которую мы хотим активировать. Мы можем назвать это authentication.service
(убедитесь, что имя свойства уже занято. Чтобы убедиться в этом, обратитесь к Списку свойств Spring). Далее мы определяем возможные значения этого свойства. В нашем случае, если бы мы рассматривали только Service X / Service Y, мы могли бы определить допустимые значения как service-x
или service-y
. Поэтому, если бы мы хотели использовать Service X для входа в систему, мы бы добавили в наше приложение следующий файл .YAML (в этом примере я использую формат файла yaml
):
authentication.service: service-x
И чтобы использовать Сервис Y для входа:
authentication.service: service-y
Шаг 2. Определите интерфейс для функциональности
Затем мы определяем интерфейс для функции входа в систему. Этот интерфейс может быть очень простым и содержать только один метод, который в основном выполняет функцию входа в систему. Обратите внимание, что все классы, которым требуется функция входа в систему, будут ссылаться только на AuthenticationService
и НЕ конкретных реализаций службы X или Y.
interface AuthenticationService { fun login() }
Шаг 3. Добавьте функцию входа в систему для конкретной службы
На этом этапе мы добавляем класс, позволяющий входить в систему с использованием службы X, и другой класс, который позволяет входить в систему с помощью службы Y. Обратите внимание, что оба этих класса будут реализовывать интерфейс, который мы определили на этапе 2.
class ServiceXAuthService: AuthenticationService { fun login() { // Functionality to execute login using Service X } } class ServiceYAuthService: AuthenticationService { fun login() { // Functionality to execute login using Service Y } }
Шаг 4. Считайте ключи службы аутентификации в объект
Чтобы использовать API или SDK из сторонней службы, нам нужно будет определить класс для хранения тех свойств, которые затем могут использоваться ServiceXAuthService
и ServiceYAuthService
для взаимодействия со службами X и Y соответственно.
Давайте также сделаем здесь еще пару предположений: Сервис X требует API-ключ для аутентификации, а Сервис Y требует API-ключа и API-секрета. Итак, мы создадим класс для хранения свойств сервиса X.
@ConstructorBinding @ConfigurationProperties(prefix = "service-x") data class ServiceXProperties( val apiKey: String )
и один для хранения свойств сервиса Y:
@ConstructorBinding @ConfigurationProperties(prefix = "service-y") data class ServiceYProperties( val apiKey: String val apiSecret: String )
Аннотации @ConstructorBinding
и @ConfigurationProperties
являются здесь ключевыми. @ConstructorBinding
используется, чтобы указать, что свойства из файла свойств или YAML должны быть прочитаны в этот объект с помощью его конструктора, а аннотация @ConfigurationProperties
указывает префикс свойства, которое необходимо прочитать из YAML или файла свойств.
Чтобы свойства были подобраны и установлены для классов должным образом, добавьте следующую зависимость в раздел зависимостей в файле build.gradle.kts
(дополнительную информацию см. В этом документе):
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
Затем мы обновим файлы для конкретной службы, чтобы предоставить учетные данные для аутентификации:
class ServiceXAuthService( private val authProperties: ServiceXProperties ): AuthenticationService { fun login() { // Functionality to execute login using Service X } } class ServiceYAuthService( private val authProperties: ServiceYProperties ): AuthenticationService { fun login() { // Functionality to execute login using Service Y } }
Шаг 5: предоставление значений свойств
Теперь, когда у нас есть все классы, мы предоставляем значения для этих свойств в application.yaml
(или application.properties
файле. Я собираюсь использовать yaml
файлы в качестве примеров здесь).
Если бы мы хотели использовать службу входа в Service X:
authentication.service: service-x service-x: apiKey: X-Key apiSecret: X-Secret
Вместо этого, если бы мы хотели использовать Сервис Y в качестве поставщика услуг входа в систему, файл application.yaml
будет выглядеть примерно так:
authentication.service: service-y service-y.apiKey: Y-Key
Шаг 6: магия настройки
Это суть этого поста. После того, как весь код для этой функциональности был добавлен, мы теперь пишем класс конфигурации, который будет решать, какой bean (ServiceXAuthService
или ServiceYAuthService
) будет доступен для всех классов, которые требуют использования функциональности входа в систему. Набор аннотаций Spring Conditional
позволяет нам прочитать свойство authentication.service
и создать ServiceXAuthService
, если значение равно service-x
, или ServiceYAuthService
, если значение равно service-y
.
// For Service X Auth Service @Configuration @EnableConfigurationProperties(ServiceXProperties::class) @ConditionalOnProperty( prefix = "authentication", name = ["service"], havingValue = "service-x" ) class ServiceXAuthConfiguration( private val serviceXProperties: ServiceXProperties ) { @Bean fun xAuthService(): AuthenticationService = ServiceXAuthService(serviceXProperties) }
И для подключения к функции входа в службу Y
// For Service Y Auth Service @Configuration @EnableConfigurationProperties(ServiceYProperties::class) @ConditionalOnProperty( prefix = "authentication", name = ["service"], havingValue = "service-y" ) class ServiceYAuthConfiguration( private val serviceYProperties: ServiceYProperties ) { @Bean fun yAuthService(): AuthenticationService = ServiceYAuthService(serviceYProperties) }
@EnableConfigurationProperties
в сочетании с @Configuration
позволяет создавать bean-компонент типа, указанного в @EnableConfigurationProperties
(в данном случае это будет либо ServiceXProperties
, либо ServiceYProperties
).
Пользователи также могут пропустить класс конфигурации и напрямую поместить эти аннотации вместе с аннотацией @Component
в классы ServiceXAuthService
и ServiceYAuthService
. Это вопрос личных предпочтений. Мне нравится использовать класс конфигурации по нескольким причинам:
- Совершенно очевидно, какие классы представлены в приложении как bean-компоненты, и эта информация хранится в центральном месте.
- Если есть дополнительные компоненты / классы, которые, возможно, требуются для определенных условий, например, если нам нужен другой класс, который будет служить служебным классом с именем
ServiceXAuthLoginHelper
, и нам это нужно только тогда, когда у насauthentication.service
установлено значениеservice-x
, мы могли бы создать два классы конфигурации один для службы X и другой для службы Y, а класс конфигурации специально для входа в службу X может содержать код для создания и предоставления обоих этих классов в качестве компонентов, а условная аннотация может применяться ко всему классу. - Благодаря встроенной поддержке Spring для тестирования классов конфигурации очень легко выполнить модульное тестирование классов конфигурации с условной логикой, чтобы убедиться, что все работает должным образом. Этот пост в блоге очень четко объясняет, как мы можем писать тесты для классов конфигурации.
Существует несколько вариантов аннотации ConditionalX
, предоставляемой Spring, которая может быть основана на bean-компонентах, классах и т. Д. Дополнительные сведения см. В Annotation Types Summary
этой документации.
Шаг 7. Превращение свойства в переменную среды
К настоящему времени вы, вероятно, задаетесь вопросом, как мы можем переключаться между предпочтительными службами входа в систему без изменения кода. В конце концов, нам нужно изменить значение authentication.service
, чтобы вся условная магия работала. Что ж, это, наверное, второй по важности шаг в этом посте. Мы делаем значение authentication.service
переменной окружения, тем самым делая его вводимым извне приложения!
authentication.service
: ${authentication.service
.name}
Теперь мы можем переключаться между учетными записями служб X и Y, указав service-x
или service-y
в качестве значения для переменной среды authentication.service.name
и предоставив свойства, которые могут понадобиться каждой из этих служб, и запустить приложение, чтобы увидеть, какая служба создана и доступна для внедрения зависимостей. . Компонент другой службы не будет создан!
Чтобы узнать больше о внешней конфигурации с помощью Spring Boot, обратитесь к их документации по этой теме.
Вывод
Я надеюсь, что вы нашли этот пост в блоге информативным о том, как добиться условного поведения приложения, управляемого свойствами, с помощью Spring Boot!