Как стать автором
Обновить

Ещё раз о пропертях или откуда что берётся

Уровень сложностиСредний
Время на прочтение8 мин
Количество просмотров9.7K
Кот пытается понять, где указано, на каком порту запустится web-server
Кот пытается понять, где указано, на каком порту запустится web-server

О чём вообще речь?

Всем привет! В данной статье речь пойдёт о настраиваемых параметрах конфигурации Spring приложений. Когда я только начал изучать Spring, естественно, одним из источников знаний были готовые примеры, проекты-образцы. И меня жутко бесило, что какие-то нужные для работы приложения значения появлялись «ниоткуда». К примеру, автор какого-нибудь туториала предлагал для проверки только что созданного учебного приложения зайти на localhost по порту 8088. Откуда берётся 8088? Почему не 8089? Оказалось, что для таких настраиваемых параметров есть специальные файлы. Итак:

Какие бывают настраиваемые параметры?

Настраиваемые параметры используются самим Spring-ом, различными библиотеками и, по желанию разработчика, могут быть добавлены свои собственные. Список всех параметров Spring-а можно посмотреть здесь.

Например, за то на каком порту будет крутиться встроенный http-сервер (если мы используем Spring Web) отвечает параметр server.port. В том самом туториале из вступления в соответствующем файле server.port был равен 8088. Выглядит это (в простейшем случае) так:

server.port=8088

Имя параметра может состоять (и, как правило, состоит) из нескольких частей. Например, все «спринговые» параметры начинаются со слова «spring». Кастомные (пользовательские) параметры, введённые разработчиком конечного приложения, могут начинаться, например со слова application или любого другого. В зависимости от используемого формата файла, части разделяются по-разному (см. следующий раздел). Простейший вариант, просто точками. Пример пользовательских параметров:

# Личное дело любимой собаки
application.dog.name=Полкан
application.dog.breed=овчарка
application.dog.color=коричневый
# Имя любимого кота
application.cat.name=Мурзик

Какие бывают источники настраиваемых параметров?

Настраиваемые параметры хранятся в файле, который может называться по разному:

  • application.<…> — базовый вариант по умолчанию

  • <any-name> — имя файла и путь к нему определяется аннотацией @PropertySource

  • bootstrap.<...> — начальные значения параметров при использовании spring-cloud

  • <application-name>.<...> — совпадает с именем приложения, доступно через config-сервер (актуально для Spring Cloud)

Каждый из этих вариантов может быть в формате key/value или в yml(yaml) формате. В случае с key/value файл должен иметь расширение properties и, как можно догадаться, каждая строчка должна выглядеть как «key=value». Строки-комментарии начинаются с символа «#». Самый распространённым вариант, когда файл параметров называется application.properties.

Пример содержимого в предыдущем разделе.

Yml(yaml)-формат представляет собой дерево, где каждая часть имени параметра является именем узла, а значение листом. Имя каждого узла пишется с новой строки и заканчивается двоеточием. Каждый дочерний узел располагается на два пробела правее родительского. Строки-комментарии также начинаются с символа «#». Выглядит это всё как-то так:

application:
  # Личное дело любимой собаки
  dog:
    name: Полкан
    breed: овчарка
    color: коричневый
  # Имя любимого кота
  cat:
    name: Мурзик
...

Будьте внимательны, количество пробелов в отступах для yaml-формата имеет значение!

Первый вариант application.property или application.yml «из коробки» работает только в Spring Boot. В классическом Spring аннотация @PropertySource обязательна. Она ставится на любой configuration-класс (класс, уже помеченный аннотацией @Configuration), в котором предполагается доступ к настраиваемым параметрам, и выглядит так:

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { … }

Также, Spring Boot позволяет иметь несколько вариантов настроек в разных файлах. В этом случае имя файла должно соответствовать следующему шаблону: application.<profile-name>.properties, ну или application.<profile-name>.yml. Актуальный вариант будет выбран в зависимости от активного spring-профиля. Что это такое, в этой статье не рассматривается, но можно почитать, например, здесь.

Где находятся эти источники настраиваемых параметров?

По умолчанию такие файлы должны лежать в classpath. Чаще всего в папке «src/main/resources». Если есть желание сложить настройки в файл с другим именем и/или по другому пути, необходимо перед классом, помеченным аннотацией @Configuration или перед тем классом, в котором планируется использовать настраиваемые параметры, указать уже знакомую аннотацию @PropertySource с путём и именем файла параметров, например так:

@PropertySource("file:/home/alex/tmp/temporary.properties")

Также в качестве источника файлов конфигурации при использовании SpringBoot может выступать config-server. Как приложение узнаёт, по какому url-у искать config-server? Сначала оно находит локальный config для загрузки базовых параметров, как раз таких, как путь к config-server-у. Этот файл должен лежать в classpath и называться application.properties или application.yml или application.yaml. В зависимости от версии Spring Сloud, имя может быть не application, а bootstrap. Допустимые расширения имени файла такие же.

В этом файле Spring ищет параметр с именем (в формате через точку) «spring.cloud.config.uri» или «spring.cloud.config.discovery.serviceId». Пример:

spring.cloud.config.uri=http://localhost:8888

или

spring.cloud.config.discovery.serviceId=config

Второй вариант, в случае использования Registry and Discovery Service (что это за зверь, можно почитать здесь).

Как использовать настраиваемые параметры (получать к ним доступ)?

Есть несколько способов. С помощью аннотации @Value, аннотации @ConfigurationProperties или же с помощью спрингового интерфейса Environment, вернее, методов классов, которые он расширяет. Разберём подробнее все варианты.

  • @Value

Этот способ проще всего. Достаточно указать эту аннотацию перед полем класса и при создании bean-а Spring проинициализирует это поле значением из config-файла. Пример:

@Value("${application.dog.name}")
private String dogName;

Если конфиг такой, как в нашем примере из начала статьи, то после создания bean-а для класса, у которого есть поле dogName, это поле будет равно «Полкан». У аннотации @Value есть ещё одна очень полезная возможность. Перед закрывающей фигурной скобкой через двоеточие после имени параметра можно задать его дефолтное значение. Т.е., если в конфиге указанное свойство не найдётся, то поле, помеченное такой аннотацией будет равно этому значению. Пример:

@Value("${application.dog.size:маленькая собачка}")
private String dogSize;

Так как в нашем конфиге параметра application.dog.size нет, поле dogSize будет проинициализировано по-умолчанию значением «маленькая собачка».

  • @ConfigurationProperties

Здесь для доступа к свойствам, прописанном в config-е надо создать специальный класс, помеченный данной аннотацией. Далее, поля этого класса будут соответствовать свойствам config-а. Причём соответствие будет устанавливаться автоматически, на основании имён. Причём имена должны быть похожи «приблизительно». Последняя возможность называется Relaxed binding.

Для того, чтобы было удобнее, в аннотации можно задать префикс, с которого будут начинаться свойства из конфига, привязанные к полям аннотированного класса. Немного запутанно? Давайте посмотрим на примере. Config возьмём знакомый, про Полкана и Мурзика. Вот как будут выглядеть соответствующие классы:

@Data // Это аннотация lombok, которая автоматически генерирует getter-ы и setter-ы
@Configuration // Аннотация Spring, благодаря которой автоматически будет создан been
@ConfigurationProperties(prefix = "application.dog")
public class DogConfig {
    private String name;
    private String breed;
    private String color;
}
@Data // Это аннотация lombok, которая автоматически генерирует getter-ы и 
//setter-ы
@Configuration // Аннотация Spring, благодаря которой автоматически 
//будет создан been
@ConfigurationProperties(prefix = "application.cat")
public class CatConfig {
    private String name;
}

И доступ к нашим настройкам из классов-потребителей будет выглядеть так:

@Component
public class ConfigConsumer {
    @Autowired
    private DogConfig dogConfig;

    @Autowired
    private CatConfig catConfig;

    public void printConfiguration() {
        System.out.println(dogConfig.getName());
        System.out.println(dogConfig.getBreed());
        System.out.println(dogConfig.getColor());

        System.out.println(catConfig.getName());
    }
}

Ну и main:

@SpringBootApplication
// @EnableConfigurationProperties(AppConfig.class) - требуется, если 
// НЕ Spring Boot
public class SpringPropertiesApplication {
      public static void main(String[] args) {
	ApplicationContext context =
                   SpringApplication.run(SpringPropertiesApplication.class, args);
	context.getBean(ConfigConsumer.class).printConfiguration();
	System.exit(0);
      }
}
  • Интерфейс Environment

Spring автоматически создаёт bean типа Environment. И если его заавтоварить, то через него можно получить доступ и к настраиваемым параметрам и к переменным окружения среды исполнения приложения. Пример:

@Component
public class ConfigConsumer {
 
    @Autowired
    private Environment env;

    public void printConfiguration() {
 
        System.out.println(env.getProperty("HOME"));
        System.out.println(env.getProperty("application.dog.name"));
   }
}

Выполнение приведённого ранее main-а выведет в моём случае:

/home/alex
Полкан

Какие есть нюансы?

  1. Аннотация @Value ставится на поле класса, аннотация @ConfigurationProperties — на класс.

  2. Использовать настраиваемые параметры можно не везде. Например, в конструкторе задействовать их напрямую не получится. Это связано с жизненным циклом spring-приложения. Дело в том, что для того, чтобы параметры были доступны, spring должен обработать наши конфиги. А он делает это непосредственно перед созданием бинов, вернее, перед помещением их в контекст, уже после вызова конструктора. Тем не менее, если класс, в конструкторе которого мы хотим задействовать настраиваемый параметр, сам является бином spring (помечен аннотацией @Component или аналогичной) и конструктор с параметром, то это ограничение можно обойти. Выглядеть это будет так:

@Data
@Component // Bean Spring
public class ParamInConstructor {
    private String priority;

    @Autowired
    public ParamInConstructor(@Value("${priority:normal}") String priority) {
        this.priority = priority;
    }
}
  1. Имеется нюанс с национальными алфавитами. Все значения настраиваемых параметров в config-файлах должны быть в кодировке ISO 8859-1, иначе мы рискуем получить кракозябры в работающем приложении. К счастью, в современных IDE присутствует функция автоматической перекодировки, которая называется native-to-ascii. Если её включить, то об этой проблеме можно не думать. В IntelyJ Idea версии 2020.3.4 автоперекодировка включается здесь:

Настройка native-to-ascii в IDE Idea
Настройка native-to-ascii в IDE Idea
  1. При определении параметра в config-е можно присвоить ему значение переменной окружения. Допустим, мы хотим иметь возможность, задать имя нашей любимой собаки в переменной окружения «DOG». Но, если те, кто будет разворачивать наше приложение полностью нам доверяют и не собираются создавать никаких переменных окружения, то должно подставляться имя по-умолчанию. Делается это так:

application.dog.name=${DOG:Полкан}
  1. Значение параметра может быть многострочным. Для этого в месте переноса нужно вставить «\n», что означает перенос строки:

application.cat.characteristics=Big \n Brown Soft Lazy

И наоборот, возможна ситуация, когда значение параметра представляет собой одну, но очень длинную строку. Которая не влезает в ширину экрана. Для удобства записать её можно в нескольких строках, но параметр будет содержать всё в одной. Для этого в месте перехода на новую строку надо поставить обратный слэш «\»:

application.cat.characteristics=Big \
  Brown \
  Soft \
  Lazy

Параметр application.cat.characteristics в примере будет содержать одну строку, хотя записан в нескольких.

Что осталось?

Ну, наверное, осталось ещё много всего, но то, что хотелось бы упомянуть, но на чём останавливаться подробно не будем:

  • Meta-data. В случае с доступом к настраиваемым параметрам через @ConfigurationProperties есть возможность использовать meta-data. Это подробная информация о настраиваемых параметрах, помогающая среде разработки (IDE) выдавать контекстные подсказки, предлагать автодополнение, предупреждать о несоответствии типов и т.д.

  • SpEL evaluation. Эта фича, наоборот доступна при использовании аннотации @Value. SpEL – Spring Expression Language – язык выражений Spring. Очень полезная возможность, позволяющая, например раскладывать значение параметра по элементам списка (ArrayList):

arrayOfStrings=cat, dog, bear
@Value("#{'${arrayOfStrings}'.split(',')}")
private List<String> listOfStrings;

Заключение

Данная статья не претендует на всеобъемлющее руководство по использованию настраиваемых параметров Spring. Скорее она призвана помочь «находить концы» в чужом коде тем, кто по каким-то причинам пытается в нём разобраться.

Полезные ссылки:

Настраиваемые параметры Spring

Spring-профили

Сравнение @ConfigurationProperties и @Value

Работа с параметрами через @ConfigurationProperties

Spring Cloud: Registry and Discovery Service

SpEL в @Value для работы с ArrayList и HashMap

Руководство по Spring аннотации @Value

Теги:
Хабы:
Всего голосов 13: ↑11 и ↓2+9
Комментарии9

Публикации

Истории

Работа

Java разработчик
361 вакансия

Ближайшие события

Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург