Конфигурационные файлы в Spring Boot

Вернуться назад

26 апреля 2019

Значения параметров системы удобно отделять от программного кода, чтобы можно было их менять без перекомпиляции всего приложения. Spring Boot предоставляет нам удобный способ работы с конфигурационными файлами. Ниже мы рассмотрим несколько случаев, начиная с самого простого.

Исходники доступны по ссылке в конце этой статьи.

Одиночные параметры

Отдельное свойство можно внедрить в любой компонент Spring при помощи аннотации @Value.

Предположим, у нас есть простейшее Spring Boot приложение, в котором есть rest-контроллер с методами.

Добавим метод, который в ответ возвращает приветственный текст для пользователя, а имя пользователя будем брать из конфига.

@RestController
@RequestMapping("/value")
public class ValueController {

    @Value("${very.important.name}")
    private String name;

    @GetMapping("/hello")
    public String getHelloText() {
        return String.format("Привет, %s!", name);
    }
}

Здесь аннотация @Value подставляет значение для поля name из конфигурационного файла application.yml. Сам файл может выглядеть следующим образом (в формате yaml):

very:
  important:
    name: Юзер

Формат yaml является наиболее современным и компактным, однако вы можете использовать и другие форматы (текст, xml).

Чтобы увидеть результат работы метода, запустите приложение и откройте в браузере http://127.0.0.1:8080/value/hello.

Вы можете подставлять не только текстовые, но и числовые значения. Добавим в наш контроллер ещё одно поле с параметром и ещё один GET-метод.

@Value("${very.important.number}")
private int number;

@GetMapping("/number")
public String getImportantNumber() {
    return Integer.toString(number);
}

Расширим наш конфигурационный файл:

very:
  important:
    name: Юзер
    number42

Обратите внимание, что оба свойства имеют общий префикс «very.important», что наглядно отображает иерархичная структура yaml.

Мы также можем указывать значения параметров по умолчанию. Сделать это можно всё в той же аннотации @Value:

@Value("${very.important.number:43}")
private int number;

Здесь значение по умолчанию отделено от имени параметра двоеточием. Значение по умолчанию будет использоваться только тогда, когда этого параметра нет в application.yml.

Группировка параметров

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

@Component
@ConfigurationProperties(prefix = "very.important")
public class VeryImportantConfig {

    private String name;
    private int number;

    // get- и set-методы для этих свойств
}

Создадим ещё один rest-контроллер, который по функционалу будет аналогичен первому, но при этом будет использовать наш VeryImportantConfig.

@RestController
@RequestMapping(value = "/grouped")
public class GroupedValueController {

    private VeryImportantConfig veryImportantConfig;

    public GroupedValueController(VeryImportantConfig veryImportantConfig) {
        this.veryImportantConfig = veryImportantConfig;
    }

    @GetMapping("/hello")
    public String getHelloText() {
        return String.format("Привет, %s!", veryImportantConfig.getName());
    }

    @GetMapping("/number")
    public String getImportantNumber() {
        return Integer.toString(veryImportantConfig.getNumber());
    }
}

Обратите внимание, что для внедрения нашего бина в данный контроллер мы не пишем явно аннотацию @Autowired. Это возможно благодаря тому, что у нас имеется контруктор, принимающий этот бин и при этом конструктор единственный.

Список значений как параметр

До сих пор мы работали с одиночными значениями. А что, если нам нужно подгрузить параметр, который является списком? Например, список рабочих дней недели. В yaml список значений записывается следующим образом:

very:
  important:
    name: Юзер
    number42
    days:
      - понедельник
      - вторник
      - среда
      - четверг
      - пятница

Каждый элемент списка начинается с тире и записывается в отдельной строке. Весь список в данном случае доступен по имени very.important.days. Добавить его в наш конфигурационный бин не менее проще, чем предыдущие значения:

@Component
@ConfigurationProperties(prefix = "very.important")
public class VeryImportantConfig {

    // другие поля
    private List<String> days;

    // другие get- и set-методы

    public List<String> getDays() {
        return days;
    }

    public void setDays(List<String> days) {
        this.days = days;
    }

В rest-контроллер добавим такой метод:

@GetMapping("/days")
public List<String> getWorkDays() {
    return veryImportantConfig.getDays();
}

Выполнив get-запрос по адресу http://127.0.0.1:8080/grouped/days мы увидим список рабочих дней недели в формате json.

Конвертер для произвольных типов данных

Со строками и списками разобрались, а что, если мы хотим хранить в конфигурационном файле и другие типы? Предположим, мы хотим хранить список праздничных дней 2019 года. Сначала добавим их в конфиг:

very:
  important:
    holidays:
      - 2019-01-01
      - 2019-01-07
      - 2019-02-23
      - 2019-03-08
      - 2019-05-01
      - 2019-05-09
      - 2019-05-12
      - 2019-11-04

Пропишем этот список в нашем конфигурационном бине:

private List<LocalDate> holidays;

public List<LocalDate> getHolidays() {
    return holidays;
}

public void setHolidays(List<LocalDate> holidays) {
    this.holidays = holidays;
}

Затем добавим метод в контроллер:

@GetMapping("/holidays")
public List<LocalDate> getHolidays() {
    return veryImportantConfig.getHolidays();
}

Если после этого запустим приложение, то при старте увидим ошибку «Failed to bind properties under 'very.important.holidays[0]' to java.time.LocalDate». Эта ошибка связана с тем, что Spring не умеет преобразовывать дату из строки в LocalDate. Для решения этой проблемы мы можем создать собственный конвертер.

@Component
@ConfigurationPropertiesBinding
public class MyCustomConverter implements Converter<String, LocalDate> {

    @Override
    public LocalDate convert(String timestamp) {
        return LocalDate.parse(timestamp);
    }
}

Мы реализовали стандартный интерфейс Converter, типизировав его исходным типом (String) и типом, в который мы преобразуем (LocalDate). Реализация единственного метода convert() довольно проста. Конвертер также должен быть снабжён аннотациями @Component и @ConfigurationPropertiesBinding, чтобы он использовался при инициализации нашего конфигурационного бина.

После этого мы можем выполнить запрос GET http://127.0.0.1:8080/grouped/holidays и увидеть список праздничных дат 2019 года.

Выводы

Конфигурационные файлы в Spring Boot обладают довольно хорошей гибкостью при работе со стандартными типами (числа и строки). А если этой гибкости не хватает, мы всегда можем написать собственный конвертер, который будет преобразовывать значения из строки в любой другой тип данных.

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

Тэги: Java, Spring Boot, rest.


Исходники


Добавить комментарий

Ваше имя:
Текст комментария: