Статьи Генератор паролей UUID MD5 Unix-время URL-encode Base64 Форматирование XML Ваш внешний IP Число прописью


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

Вернуться назад Исходники

26 апреля 2019

Тэги: rest Java Spring Boot

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

Исходники доступны на github.

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

Отдельное свойство можно внедрить в любой компонент 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: Юзер
    number: 42

Обратите внимание, что оба свойства имеют общий префикс «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: Юзер
    number: 42
    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. В любой момент вы можете изменить формат конфигурационных файлов и при этом вам не нужно будет править код вашего приложения.