Розробка

Налаштування програми — Spring Configuration Metadata

Налаштування програми за допомогою @ConfigurationProperties, як альтернатива використанню @Value.

В статті

  • Налаштування і зміни функціоналу програми через application.properties з використанням ConfigurationProperties
  • Інтеграція application.properties з IDE
  • Перевірка установок

Про відмінності між двома підходами сказано тут — ConfigurationProperties vs. Value
На картинці вище основний склад і принцип роботи. Доступні компоненти системи, це Spring компоненти, просто класи, різні константи, змінні та ін. можна вказати у файлі application.properties, при цьому ще на момент вказівки середовищем розробки будуть запропоновані варіанти, зроблені перевірки. При старті програми зазначені значення будуть перевірені на відповідність типу, обмежень і якщо все задовольняє, то буде виконаний старт програми. Наприклад дуже зручно налаштовувати функціонал програми зі списку доступних Spring компонент, нижче покажу як.
Клас властивостей
Для створення налаштування програми з використанням ConfigurationProperties, можна почати з класу властивостей. У ньому власне вказані властивості, компоненти системи, які хочемо налаштовувати.
AppProperties.java

@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, як префікс до властивості.
Клас додатки SpringApplication і pom.xml проекту

@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.звітування.outputEncoding>UTF-8</project.звітування.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 біна
Spring контекст (context.xml)

<?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.

Ось їх реалізація
DemoService

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 повідомить

Навіть якщо залишити як є і спробувати стартувати додаток, то буде цілком виразна помилка

Додавання додаткових метаданих
Додаткові метадані, це тільки для зручності роботи з додатком.properties в IDE, якщо це не треба, можна не робити. Для цього є можливість вказати в додатковому файлі підказки (hints) та ін. інформацію для середовища. Для цього скопирую створений файл spring-configuration-metadata.json в srcmainresourcesMETA-INF і переименую його в
additional-spring-configuration-metadata.json. У цьому файлі мене буде цікавити тільки секція «hints»: []
У ній можна буде перерахувати наприклад допустимі значення для demo.vehicle

"hints": [
{
 "name": "demo".vehicle",
 "values": [
{
 "value": "make A car",
 "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.
ServiceConverter

@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
additional-spring-configuration-metadata

{
 "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": "make A car",
 "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"
}
}
]
}
]
}

Related Articles

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Close