Настройка приложения с помощью @ConfigurationProperties
, как альтернатива использованию @Value
.
В статье
- Настройка и изменение функционала приложения через application.properties с использованием ConfigurationProperties
- Интеграция application.properties с IDE
- Проверка значений настроек
Про отличия между двумя подходами сказано здесь — ConfigurationProperties vs. Value
На картинке выше основной состав и принцип работы. Доступные компоненты системы, это Spring компоненты, просто классы, различные константы, переменные и пр. можно указать в файле application.properties, при этом еще на момент указания средой разработки будут предложены варианты, сделаны проверки. При старте приложения указанные значения будут проверенны на соответствие типа, ограничениям и если все удовлетворяет, то будет выполнен старт приложения. Например очень удобно настраивать функционал приложения из списка доступных Spring компонент, ниже покажу как.
Класс свойств
Для создания настройки приложения с использованием ConfigurationProperties, можно начать с класса свойств. В нем собственно указаны свойства, компоненты системы которые хотим настраивать.
@ConfigurationProperties(prefix = "demo")
@Validated
public class AppProperties {
private String vehicle;
@Max(value = 999, message = "Value 'Property' should not be greater than 999")
private Integer value;
private Map<String,Integer> contexts;
private StrategyEnum strategyEnum;
private Resource resource;
private DemoService service;
public String getVehicle() {
return vehicle;
}
public void setVehicle(String vehicle) {
this.vehicle = vehicle;
}
public Map<String, Integer> getContexts() {
return contexts;
}
public void setContexts(Map<String, Integer> contexts) {
this.contexts = contexts;
}
public StrategyEnum getStrategyEnum() {
return strategyEnum;
}
public void setStrategyEnum(StrategyEnum strategyEnum) {
this.strategyEnum = strategyEnum;
}
public Resource getResource() {
return resource;
}
public void setResource(Resource resource) {
this.resource = resource;
}
public DemoService getService() {
return service;
}
public void setService(DemoService service) {
this.service = service;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
@Override
public String toString() {
return "MyAppProperties{" +
"nvehicle=" + vehicle +
"n,contexts=" + contexts +
"n,service=" + service +
"n,value=" + value +
"n,strategyEnum=" + strategyEnum +
'}';
}
}
В классе prefix=«demo» будет использоваться в application.properties, как префикс к свойству.
@SpringBootApplication
@EnableConfigurationProperties({AppProperties.class})
@ImportResource(value= "classpath:context.xml")
public class DemoConfigProcessorApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoConfigProcessorApplication.class, args);
AppProperties properties = context.getBean(AppProperties.class);
String perform = properties.getService().perform(properties.getVehicle());
System.out.println("perform: " + perform);
System.out.println(properties.toString());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demoConfigProcessor</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demoConfigProcessor</name>
<description>Demo project for Spring Boot Configuration Processor</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Тут я объявил два spring бина
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="service1" class="com.example.demoConfigProcessor.MyDemoService1">
<description>Description MyDemoService 1</description>
</bean>
<bean id="service2" class="com.example.demoConfigProcessor.MyDemoService2">
<description>Description MyDemoService 2</description>
</bean>
</beans>
В классе AppProperties я указал ссылку на некоторый доступный сервис приложения, его я буду менять в application.properties, у меня будет две его реализации и я буду подключать одну из них в application.properties.
Вот их реализация
public interface DemoService {
String perform(String value);
}
public class MyDemoService1 implements DemoService {
@Override
public String perform(String value) {
return "Service №1: perform routine maintenance work on <" + value +">";
}
}
public class MyDemoService2 implements DemoService {
@Override
public String perform(String value) {
return "Service №2: perform routine maintenance work on <" + value +">";
}
}
Вот этого уже теперь достаточно что бы начать настраивать application.properties. Но всякий раз когда вносятся изменения в класс с ConfigurationProperties, надо сделать rebuild проекта, после чего в проекте появится файл
targetclassesMETA-INFspring-configuration-metadata.json
. Собственно его IDE использует для редактирования в файле application.properties. Его структуру я укажу в ссылке в материалах. Этот файл будет создан на основе класса AppProperties. Если теперь открыть файл application.properties и начать вводить «demo», то среда начнет показывать доступные свойства
При попытке ввести неверный тип IDE сообщит
Даже если оставить как есть и попытаться стартовать приложение, то будет вполне внятная ошибка
Добавление дополнительных метаданных
Дополнительные метаданные, это только для удобства работы с application.properties в IDE, если это не надо, можно не делать. Для этого есть возможность указать в дополнительном файле подсказки (hints) и др. информацию для среды. Для этого скопирую созданный файл spring-configuration-metadata.json в srcmainresourcesMETA-INF
и переименую его в
additional-spring-configuration-metadata.json
. В этом файле меня будет интересовать только секция «hints»: []
В ней можно будет перечислить например допустимые значения для demo.vehicle
"hints": [
{
"name": "demo.vehicle",
"values": [
{
"value": "car make A",
"description": "Car brand A is allowed."
},
{
"value": "car make B",
"description": "Car brand B is allowed."
}
]
}]
В поле «name» указано св-во «demo.vehicle», а в «values» список допустимых значений. Теперь если сделать rebuild проекта и перейти в файл application.properties, то при вводе demo.vehicle получу список допустимых значений
При вводе отличного от предложенного, но того же типа, редактор подсветит, но приложение в этом случае будет стартовать, так как это не строгое ограничение, а всего лишь подсказка.
Ранее в проекте я объявил два сервиса MyDemoService1 и MyDemoService2 оба они имплементируют интерфейс DemoService, теперь можно настроить чтобы application.properties были доступны только сервисы имплементирующие этот интерфейс и соответственно в AppProperties классе инициализировался выбранный. Для этого есть Providers их можно указать в additional-spring-configuration-metadata. Провайдеры есть нескольких типов их можно посмотреть в документации, я покажу пример для одного, — spring-bean-reference
. Этот тип показывает имена доступных bean-компонентов в текущем проекте. Список ограничивается базовым классом или интерфейсом.
Пример Providers для DemoService:
"hints": [
{
"name": "demo.service",
"providers": [
{
"name": "spring-bean-reference",
"parameters": {
"target": "com.example.demoConfigProcessor.DemoService"
}
}
]
}
]
После чего в application.properties для параметра demo.service будет доступен выбор двух сервисов, можно посмотреть их описание (description из определения).
Теперь удобно выбирать нужный сервис, менять функционал приложения. Есть один момент для объектных настроек, Spring надо немного помочь конвертировать строку которая указана в настройке, в объект. Для этого делается небольшой класс наследник от Converter.
@Component
@ConfigurationPropertiesBinding
public class ServiceConverter implements Converter<String, DemoService> {
@Autowired
private ApplicationContext applicationContext;
@Override
public DemoService convert(String source) {
return (DemoService) applicationContext.getBean(source);
}
}
На диаграмме классов проекта видно как эти сервисы отделены от основного приложения и доступны через AppProperties.
Validation property
К полям класса AppProperties можно добавить проверки доступные в рамках JSR 303. Про это я писал здесь. Получится проверяемый, удобный файл конфигурации приложения.
Вывод в консоли
Структура проекта
Полный файл additional-spring-configuration-metadata.json
{
"groups": [
{
"name": "demo",
"type": "com.example.demoConfigProcessor.AppProperties",
"sourceType": "com.example.demoConfigProcessor.AppProperties"
}
],
"properties": [
{
"name": "demo.contexts",
"type": "java.util.Map<java.lang.String,java.lang.Integer>",
"sourceType": "com.example.demoConfigProcessor.AppProperties"
},
{
"name": "demo.vehicle",
"type": "java.lang.String",
"sourceType": "com.example.demoConfigProcessor.AppProperties"
}
],
"hints": [
{
"name": "demo.vehicle",
"values": [
{
"value": "car make A",
"description": "Car brand A is allowed."
},
{
"value": "car make B",
"description": "Car brand B is allowed."
}
]
},
{
"name": "demo.service",
"providers": [
{
"name": "spring-bean-reference",
"parameters": {
"target": "com.example.demoConfigProcessor.DemoService"
}
}
]
}
]
}
Материалы Configuration Metadata
Автор: Александр Рыльков