Настройка приложения — Spring Configuration Metadata

Настройка приложения с помощью @ConfigurationProperties, как альтернатива использованию @Value.

В статье

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


image
Про отличия между двумя подходами сказано здесь — 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 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 getContexts() {
        return contexts;
    }

    public void setContexts(Map 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());
        }

}



        4.0.0

        com.example
        demoConfigProcessor
        0.0.1-SNAPSHOT
        jar

        demoConfigProcessor
        Demo project for Spring Boot Configuration Processor

        
                org.springframework.boot
                spring-boot-starter-parent
                2.1.0.RELEASE
                 
        

        
                UTF-8
                UTF-8
                1.8
        

        
                
                        org.springframework.boot
                        spring-boot-starter
                

                
                        org.springframework.boot
                        spring-boot-starter-validation
                

                
                        org.springframework.boot
                        spring-boot-configuration-processor
                        true
                
                
                        org.springframework.boot
                        spring-boot-starter-test
                        test
                
                
                        com.jayway.jsonpath
                        json-path
                
        

        
                
                        
                                org.springframework.boot
                                spring-boot-maven-plugin
                        
                
        






Тут я объявил два spring бина

Spring контекст (context.xml)




    
        Description MyDemoService 1
    

    
        Description MyDemoService 2
    




В классе AppProperties я указал ссылку на некоторый доступный сервис приложения, его я буду менять в application.properties, у меня будет две его реализации и я буду подключать одну из них в application.properties.

image

Вот их реализация

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 проекта, после чего в проекте появится файл
\target\classes\META-INF\spring-configuration-metadata.json . Собственно его IDE использует для редактирования в файле application.properties. Его структуру я укажу в ссылке в материалах. Этот файл будет создан на основе класса AppProperties. Если теперь открыть файл application.properties и начать вводить «demo», то среда начнет показывать доступные свойства

image

При попытке ввести неверный тип IDE сообщит

image

Даже если оставить как есть и попытаться стартовать приложение, то будет вполне внятная ошибка

image

Добавление дополнительных метаданных


Дополнительные метаданные, это только для удобства работы с application.properties в IDE, если это не надо, можно не делать. Для этого есть возможность указать в дополнительном файле подсказки (hints) и др. информацию для среды. Для этого скопирую созданный файл spring-configuration-metadata.json в \src\main\resources\META-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 получу список допустимых значений

image

При вводе отличного от предложенного, но того же типа, редактор подсветит, но приложение в этом случае будет стартовать, так как это не строгое ограничение, а всего лишь подсказка.

image

Ранее в проекте я объявил два сервиса 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 из определения).

image

Теперь удобно выбирать нужный сервис, менять функционал приложения. Есть один момент для объектных настроек, Spring надо немного помочь конвертировать строку которая указана в настройке, в объект. Для этого делается небольшой класс наследник от Converter.

ServiceConverter
@Component
@ConfigurationPropertiesBinding
public class ServiceConverter implements Converter {

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public DemoService convert(String source) {
      return (DemoService) applicationContext.getBean(source);
    }
}



На диаграмме классов проекта видно как эти сервисы отделены от основного приложения и доступны через AppProperties.

image

Validation property

К полям класса AppProperties можно добавить проверки доступные в рамках JSR 303. Про это я писал здесь. Получится проверяемый, удобный файл конфигурации приложения.

Вывод в консоли

image

Структура проекта

image

Полный файл 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",
      "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

© Habrahabr.ru