image

А что, если я скажу, что подобное

#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 и других интерпретаторах, которые не допускают переменных, содержащих символ точки и/или символ дефиса. Также допускается вариация имён в верхнем регистре для более идиоматического для оболочки представления.

Оригинальный текст
Specialization of MapPropertySource designed for use with system environment variables. Compensates for constraints in Bash and other shells that do not allow for variables containing the period character and/or hyphen character; also allows for uppercase variations on property names for more idiomatic shell use.

А чё так можно было что ли?


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

  • Случайные значения:

    #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)


  1. PositiveAlex
    15.11.2021 08:08

    Спасибо, а почему в последнем примере кода после равно ничего нет?

    Как мне кажется, это не интуитивно выглядит и нужен коментарий, что это магия спринга. А то как-то как будто ты ничего не приравнял к свойству


    1. Frechet Автор
      15.11.2021 08:20
      +1

      В этом и заключалась неожиданность поведения для меня, достаточно обьявить переменную окружения с тем же именем, что и свойство. Далее, благодаря SystemEnvironmentPropertySource, Spring поищет значения свойств в переменных окружения перебирая различные варианты с ./_/- и регистрами. Если будет найдено значение, то им будет проинизиализированно свойство.

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


      1. Paskin
        15.11.2021 08:57
        +3

        А пользователи все будут удивляться - почему старт Spring-приложения с каждой версией занимает все больше времени...


  1. DustCn
    15.11.2021 09:20
    +5

    Знаете, неважно как там деды и прочее, а вот привычка все что нужно проинициализировать - прописать это явно, это хорошая привычка.

    В этом плане "магия" оно хорошо конечно, но лучше явно задавать что куда и зачем. Иначе когда магия вдруг кончится (новая версия, проблемы с локалью, фаза луны) искать причину её перебоев тот еще геморрой.


    1. PositiveAlex
      15.11.2021 09:21
      +1

      Яростно поддерживаю


    1. Frechet Автор
      15.11.2021 09:53

      Не спорю, возможность есть и такая и такая, видимо решать нам самим как делать.

      Показал какая может быть проблема из-за такого поведения, когда из-за совпадения имён происходит перетирание значения, при этом всё работает, но не так как задуманно было.


    1. maxzh83
      15.11.2021 11:25
      +2

      В этом плане "магия" оно хорошо конечно, но лучше явно задавать что куда и зачем.

      Грань тонкая. С одной стороны, да, чем магии меньше, тем лучше. С другой стороны, если вы взяли какой-то инструмент с магией (а Spring именно такой), то неплохого бы знать его возможности. И тогда это перестает быть магией. А то так можно прийти к тому, что и DI окажется магией и лучше нам все делать явно.


  1. tsypanov
    18.11.2021 00:32

    Интересная мысль пришла в голову при просмотре вот про этого участка:

    ?someProperty=${PROPERTY}

    Здесь мы передаём в ссылку переменную окружения, содержимое которой, насколько я понимаю, никак не фильтруется. Можно ли в сделать SQL-инъекцию через подобную переменную окружения, используя, например, validation query?


    1. Frechet Автор
      18.11.2021 08:57

      Могу предположить, что поскольку это конфигурация свойств драйвера, то обработка будет выполнена на программном уровне и иньекция не сработает.


      1. 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"

        Тогда при следующем перезапуске приложения оно грохнет базу.


  1. therealalexz
    18.11.2021 15:07

    спасибо за статью