Ещё раз о пропертях или откуда что берётся
Кот пытается понять, где указано, на каком порту запустится 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.<…> — базовый вариант по умолчанию
— имя файла и путь к нему определяется аннотацией @PropertySource bootstrap.<...> — начальные значения параметров при использовании spring-cloud
.<...> — совпадает с именем приложения, доступно через 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.
Где находятся эти источники настраиваемых параметров?
По умолчанию такие файлы должны лежать в 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, вернее, методов классов, которые он расширяет. Разберём подробнее все варианты.
Этот способ проще всего. Достаточно указать эту аннотацию перед полем класса и при создании 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);
}
}
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
Полкан
Какие есть нюансы?
Аннотация @Value ставится на поле класса, аннотация @ConfigurationProperties — на класс.
Использовать настраиваемые параметры можно не везде. Например, в конструкторе задействовать их напрямую не получится. Это связано с жизненным циклом 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;
}
}
Имеется нюанс с национальными алфавитами. Все значения настраиваемых параметров в config-файлах должны быть в кодировке ISO 8859–1, иначе мы рискуем получить кракозябры в работающем приложении. К счастью, в современных IDE присутствует функция автоматической перекодировки, которая называется native-to-ascii. Если её включить, то об этой проблеме можно не думать. В IntelyJ Idea версии 2020.3.4 автоперекодировка включается здесь:
Настройка native-to-ascii в IDE Idea
При определении параметра в config-е можно присвоить ему значение переменной окружения. Допустим, мы хотим иметь возможность, задать имя нашей любимой собаки в переменной окружения «DOG». Но, если те, кто будет разворачивать наше приложение полностью нам доверяют и не собираются создавать никаких переменных окружения, то должно подставляться имя по-умолчанию. Делается это так:
application.dog.name=${DOG:Полкан}
Значение параметра может быть многострочным. Для этого в месте переноса нужно вставить »\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 listOfStrings;
Заключение
Данная статья не претендует на всеобъемлющее руководство по использованию настраиваемых параметров Spring. Скорее она призвана помочь «находить концы» в чужом коде тем, кто по каким-то причинам пытается в нём разобраться.
Полезные ссылки:
Настраиваемые параметры Spring
Spring-профили
Сравнение @ConfigurationProperties и @Value
Работа с параметрами через @ConfigurationProperties
Spring Cloud: Registry and Discovery Service
SpEL в @Value для работы с ArrayList и HashMap
Руководство по Spring аннотации @Value