А что, если я скажу, что подобное
#application.properties
spring.datasource.url=${SPRING_DATASOURCE_URL}?someProperty=${PROPERTY}
содержит ошибку. Не согласны? Разбор под катом.
Жили и не тужили
Как часто работая с проектом, аккуратно завёрнутым в Docker и CI, вам доводилось видеть нечто такое:
#application.properties
spring.datasource.url=${SPRING_DATASOURCE_URL}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver
Решение довольно прозрачно, чтобы определить свойства нашего модуля, зададим им значения из переменных окружения, которые наш CI даст определить для каждой среды своими значениями.
Сюрприз
В нашем разнообразном мире сотни самописных микросервисов и разнородных команд, не задаваясь вопросами, копируя решения из одного проекта в другой, потребовалось немного отойти от привычного состава
#application.properties
spring.datasource.url=${SPRING_DATASOURCE_URL}?someProperty=${PROPERTY}
Переменные окружения проинициализированны, приложение работает с заданным URL, но наше свойство не проставляется.
Расследование
Оказывается уже с версии 3.1 Spring использует переменные окружения как источник свойств, а чтобы нам их можно было определить в bash привычным образом, любезно учитывает верхний регистр и знак подчёркивания вместо точки.
Выдержка из реализации источника свойств Spring приложений, класса SystemEnvironmentPropertySource, в вольном переводе автора:
Спецификация источника, определяющего свойства из системных переменных среды. Для компенсации ограничений в Bash и других интерпретаторах, которые не допускают переменных, содержащих символ точки и/или символ дефиса. Также допускается вариация имён в верхнем регистре для более идиоматического для оболочки представления.
А чё так можно было что ли?
Подробное описание возможностей легко найти на эльфийских просторах интернета. Оставлю небольшую выдержку, что ещё уже давно можно взять на вооружение.
- Случайные значения:
#application.properties random.number=${random.int} random.long=${random.long} random.uuid=${random.uuid}
- Значение по умолчанию и/или короткое имя:
#application.properties server.port=${port:8080}
- В дополнение к application.properties возможность переопределить часть переменных для приложения запущенного с некоторым профилем myprofile в дополнительном файле application-myprofile.properties
За большим материалом не поленитесь прочитать документацию.
Заключение
Знай свой инструмент и не занимайся суеверным программированием делая так, потому что твои
#bash
export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost/test?environmentDependencyProperty=value
#application.properties
spring.datasource.url=
Будет прекрасно работать, без необходимости явно описывать переменные окружения в значениях свойства.
Комментарии (12)
DustCn
15.11.2021 09:20+5Знаете, неважно как там деды и прочее, а вот привычка все что нужно проинициализировать - прописать это явно, это хорошая привычка.
В этом плане "магия" оно хорошо конечно, но лучше явно задавать что куда и зачем. Иначе когда магия вдруг кончится (новая версия, проблемы с локалью, фаза луны) искать причину её перебоев тот еще геморрой.
Frechet Автор
15.11.2021 09:53Не спорю, возможность есть и такая и такая, видимо решать нам самим как делать.
Показал какая может быть проблема из-за такого поведения, когда из-за совпадения имён происходит перетирание значения, при этом всё работает, но не так как задуманно было.
maxzh83
15.11.2021 11:25+2В этом плане "магия" оно хорошо конечно, но лучше явно задавать что куда и зачем.
Грань тонкая. С одной стороны, да, чем магии меньше, тем лучше. С другой стороны, если вы взяли какой-то инструмент с магией (а Spring именно такой), то неплохого бы знать его возможности. И тогда это перестает быть магией. А то так можно прийти к тому, что и DI окажется магией и лучше нам все делать явно.
tsypanov
18.11.2021 00:32Интересная мысль пришла в голову при просмотре вот про этого участка:
?someProperty=${PROPERTY}
Здесь мы передаём в ссылку переменную окружения, содержимое которой, насколько я понимаю, никак не фильтруется. Можно ли в сделать SQL-инъекцию через подобную переменную окружения, используя, например, validation query?
Frechet Автор
18.11.2021 08:57Могу предположить, что поскольку это конфигурация свойств драйвера, то обработка будет выполнена на программном уровне и иньекция не сработает.
tsypanov
18.11.2021 11:20Нужно будет попробовать. Написать что-то вроде
spring: datasource: hikari: connection-test-query: ${VALIDATION_QUERY:"select 1 from dual"}
Покуда переменная окружения не задана, то всё хорошо. Однако, ничто не мешает мне задать её и прописать что-то вроде
export VALIDATION_QUERY='select 1 from dual; drop database my_prod_db"
Тогда при следующем перезапуске приложения оно грохнет базу.
PositiveAlex
Спасибо, а почему в последнем примере кода после равно ничего нет?
Как мне кажется, это не интуитивно выглядит и нужен коментарий, что это магия спринга. А то как-то как будто ты ничего не приравнял к свойству
Frechet Автор
В этом и заключалась неожиданность поведения для меня, достаточно обьявить переменную окружения с тем же именем, что и свойство. Далее, благодаря SystemEnvironmentPropertySource, Spring поищет значения свойств в переменных окружения перебирая различные варианты с ./_/- и регистрами. Если будет найдено значение, то им будет проинизиализированно свойство.
При этом, если в свойстве будет фигурировать несколько переменных окружения, то значение свойства всё равно будет из-за приоритета подстановок перетёрто значением в переменной с совпадающей с именем.
Paskin
А пользователи все будут удивляться - почему старт Spring-приложения с каждой версией занимает все больше времени...