Я описываю Spring Boot как эквивалент Apple для среды разработки программного обеспечения. 🍎 Причина в том, что он хорошо спроектирован и позволяет писать меньше и чистый код. В смысле, он просто элегантный, как у Apple. (Я могу быть здесь немного предвзятым, поскольку я команда Apple).

Spring Boot поставляется с множеством аннотаций, которые значительно упрощают нашу жизнь программистов. 🙂 Один из них, который я использую довольно часто, - это @ConfigurationProperties. Мне он показался весьма полезным, поэтому я хочу показать вам, как использовать его в вашем приложении.

В частности, вы узнаете, как определить конфигурации вашего приложения как dataclass, которым будет управлять Spring. Мы будем использовать комбинацию аннотации @ConfigurationProperties и файла application.yml для создания конфигурации dataclass.

Определение класса / классов конфигурации удобно. Нам нужно определить его только один раз, и после этого мы можем использовать его столько раз, сколько захотим, в разных классах, которые в нем нуждаются.

Чтобы следовать этому руководству, вы можете клонировать это репозиторий Github. Обратите внимание, что начало этого руководства - это фиксация, поэтому проверьте этот коммит, если вы хотите собрать код самостоятельно по мере прохождения этого руководства. Если нет, вы можете просто клонировать репо и читать дальше. 🙂

Приложение, содержащееся в репозитории, представляет собой приложение Kotlin, созданное с использованием Spring Boot и Spring Webflux и связанное с DynamoDB. Если вам интересно, вы можете прочитать эту статью, чтобы узнать, как изначально создавалось приложение.

Создание класса данных DynamoConfigProperties

Давайте посмотрим на наш текущий DynamoClientProperties класс. Вы можете видеть, что он принимает region и endpoint в качестве 2 x аргументов для своего конструктора класса. Оба этих значения будут введены Spring через аннотацию @Value, которая просматривает файл application.yml, расположенный в каталоге src/main/kotlin/resources, по пути, указанному в его аргументе.

С другой стороны, давайте проверим содержание нашего application.yml файла. Довольно просто и понятно. Он просто определяет конфигурацию для наших классов, связанных с DynamoDB.

Теперь, если мы просто оставим приложение как есть, все будет работать нормально. На самом деле ничего менять не нужно. Но представьте, что наша конфигурация состоит из нескольких значений, а не только из двух (в нашем случае это всего лишь region и endpoint), и та же самая конфигурация используется в нескольких классах, а не только в одном классе (например, в DynamoClientProperties.kt).

Когда это так, это означает, что нам нужно писать одни и те же @Values операторы в разных местах. Вот почему вместо этого мы будем использовать @ConfigurationProperties. Он позволяет нам определить dataclass, содержащий все значения конфигурации для конкретной цели (например, конфигурацию, связанную с DynamoDB), только один раз, в одном месте, и мы можем использовать его в любом количестве других мест / классов, в котором нам нужно.

Создайте новый файл Kotlin и назовите его DynamoConfigProperties.kt. Содержимое файла будет таким.

Сразу видно, что вы пишете меньше кода, чтобы получить нужные значения. Затем вы можете перейти к классу DynamoClientProperties и ввести DynamoConfigProperties вместо region и endpoint. Будет так.

Это намного проще и чище, правда? Еще одно преимущество заключается в том, что если вам когда-либо понадобится какое-либо из значений customerTableName, region или endpoint, вы можете просто передать DynamoConfigProperties, который, кстати, является компонентом Spring, поэтому он инициализируется только один раз при запуске приложения Spring Boot.

Запустить приложение

Теперь, если вы запустите main функцию приложения в DynamodemoApplication.kt из IntelliJ, все будет работать, как до внесения изменений. Все хорошо, ребята. 😃

Вы можете продолжить и следовать этому руководству, чтобы проверить, что взаимодействие с DynamoDB по-прежнему работает должным образом (извините, это последняя часть руководства, но Medium еще не поддерживает привязку 😜).

Дополнительное примечание о написании класса данных DynamoConfigProperties

Когда вы посмотрите, как был написан DynamoConfigProperties, вы можете немного сомневаться или подумать, что это немного беспорядочно (или некрасиво), поскольку мы объявили поля как var вместо val, несмотря на то, что значения никогда не будут меняться (или не должны изменить, я бы сказал).

Кроме того, нам нужно предоставить значение по умолчанию либо пустую строку "" (или любую String, на самом деле) или null (для этого тип полей в DynamoConfigProperties необходимо изменить на String?).

Если вы объявите их как val или не предоставите значение по умолчанию и запустите приложение, вы увидите следующую ошибку.

2020-01-16 07:21:57.883  WARN 97395 --- [           main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerHandler' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/CustomerHandler.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerRepo' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/CustomerRepo.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dynamoClientProperties' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/DynamoClientProperties.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dynamoConfigProperties' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/DynamoConfigProperties.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2020-01-16 07:21:57.960  INFO 97395 --- [           main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-01-16 07:21:58.085 ERROR 97395 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in io.codebrews.dynamodemo.DynamoConfigProperties required a bean of type 'java.lang.String' that could not be found.
Action:
Consider defining a bean of type 'java.lang.String' in your configuration.
Process finished with exit code 1

Вы можете видеть, что Spring жалуется на конструктор нашего DynamoConfigProperties. 😫

Думаю, нам просто нужно разобраться с тем, насколько «некрасиво» была написана наша конфигурация dataclass, не так ли? 🤷‍♂

НЕТ, мы этого не делаем. На помощь приходит Spring Boot 2.2.x.RELEASE. 😊

Использование аннотации ConstructorBinding для класса данных конфигурации

Если в вашем проекте используется среда Spring Boot версии 2.2.0.RELEASE или выше, вы можете изменить способ написания DynamoConfigProperties следующим образом.

Теперь продолжайте и снова запустите main функцию приложения. Вы должны увидеть следующее исключение, которое вам бросят. 😦

2020-01-16 13:29:03.367  WARN 87367 --- [           main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerHandler' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/CustomerHandler.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerRepo' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/CustomerRepo.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dynamoClientProperties' defined in file [/Users/billyde/demo/dynamodemo/out/production/classes/io/codebrews/dynamodemo/DynamoClientProperties.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'io.codebrews.dynamodemo.DynamoConfigProperties' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2020-01-16 13:29:03.422  INFO 87367 --- [           main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-01-16 13:29:03.498 ERROR 87367 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in io.codebrews.dynamodemo.DynamoClientProperties required a bean of type 'io.codebrews.dynamodemo.DynamoConfigProperties' that could not be found.
Action:
Consider defining a bean of type 'io.codebrews.dynamodemo.DynamoConfigProperties' in your configuration.
Process finished with exit code 1

К сожалению, Spring будет жаловаться вам, что не может найти bean-компонент DynamoConfigProperties, который требуется нашему приложению для запуска. Причина этого в том, что Spring не сканирует классы, помеченные @ConfigurationProperties.

Наш исходный DynamoConfigProperties работает нормально, потому что мы аннотируем его с помощью @Component, который сканируется Spring при построении контекста приложения.

Я знаю, о чем вы думаете ... Вы собираетесь добавить аннотацию @Component к обновленному DynamoConfigProperties, не так ли? Но, к сожалению, это тоже не сработает, и при запуске приложения выдает эту ошибку.

2020-01-16 13:45:06.021  WARN 92292 --- [           main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dynamoConfigProperties': @EnableConfigurationProperties or @ConfigurationPropertiesScan must be used to add @ConstructorBinding type io.codebrews.dynamodemo.DynamoConfigProperties
2020-01-16 13:45:06.031  INFO 92292 --- [           main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-01-16 13:45:06.039 ERROR 92292 --- [           main] o.s.boot.SpringApplication               : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dynamoConfigProperties': @EnableConfigurationProperties or @ConfigurationPropertiesScan must be used to add @ConstructorBinding type io.codebrews.dynamodemo.DynamoConfigProperties
 at org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator.validate(ConfigurationPropertiesBeanDefinitionValidator.java:66) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator.postProcessBeanFactory(ConfigurationPropertiesBeanDefinitionValidator.java:45) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:286) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
 at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:174) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:706) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.2.2.RELEASE.jar:5.2.2.RELEASE]
 at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) [spring-boot-2.2.2.RELEASE.jar:2.2.2.RELEASE]
 at io.codebrews.dynamodemo.DynamodemoApplicationKt.main(DynamodemoApplication.kt:15) [classes/:na]
Process finished with exit code 1

Итак, что нам теперь делать? Мы улучшили способ написания нашего кода, однако наше приложение вылетает! Должны ли мы вернуться к старым путям?

Точно нет! 🙅‍♂

Вот как это исправить. (На самом деле это очень просто).

Применение ConfigurationPropertiesScan или EnableConfigurationProperties к нашему основному классу приложения

В силу другой аннотации Spring Boot - @ConfigurationPropertiesScan. Сообщение об ошибке из случая, когда мы пытались аннотировать обновленный DynamoConfigProperties с помощью @Component, на самом деле говорит нам использовать либо @ConfigurationPropertiesScan, либо @EnableConfigurationProperties.

В этом руководстве мы продолжим работу с @ConfigurationPropertiesScan. Итак, давайте добавим эту аннотацию к нашему классу DynamodemoApplication вот так.

В качестве альтернативы, если вы хотите использовать вместо этого @EnableConfigurationProperties, вы это делаете так. Для его работы требуется небольшой дополнительный шаг, т.е. вам нужно явно указать, какой класс конфигурации вы хотите включить. В нашем случае это DynamoConfigProperties.

Если у вас есть другой класс / классы конфигурации, которые вы хотите включить, вам необходимо указать их все, разделив их запятой, например @EnableConfigurationProperties(DynamoConfigProperties::class, KafkaConfigProperties::class).

После применения любого из вышеперечисленных действий снова запустите функцию main. На этот раз больше никаких уловок от меня. 😜

Вуаля! Теперь все работает.

Молодцы ребята! 👍 👍

использованная литература

Приложение Github - здесь

Учебник о том, как изначально создавалось приложение - здесь

Официальная документация Spring Boot на @ConfigurationProperties - здесь.