JavaRush /Курсы /Spring Boot /SPRING_APPLICATION_JSON

SPRING_APPLICATION_JSON: свойства из JSON

Spring Boot
14 уровень , 2 лекция
Открыта

1. Когда JSON удобнее переменных окружения

Представьте, что вы запускаете catalog-service не «как обычно», а в маленьком демонстрационном режиме. Хочется быстро поменять заголовок каталога, включить технический режим (maintenance), уменьшить лимит featured-курсов и, например, подвинуть порт, чтобы не конфликтовать с другим сервисом на вашем ноутбуке. Технически это всё — обычные свойства. Практически — руками печатать их по одному становится утомительно.

Три launch-time канала уже понятны: env vars, -D и --key=value. Но как только нужно перекинуть не одно свойство, а пачку связанных значений, передавать их по одному становится неудобно. Отсюда и появляется JSON-блок: не новая модель конфигурации, а более компактный override поверх уже знакомой карты.

Чтобы почувствовать боль, достаточно посмотреть на такой запуск. Он рабочий, но если вы ошиблись в одном символе, приложение просто спокойно проигнорирует неправильный ключ и сделает вид, что так и было задумано (а вы потом два часа спорите с реальностью).

# Длинный запуск: много отдельных переменных окружения
APP_CATALOG_TITLE="Demo Catalog" \
APP_CATALOG_MAINTENANCE_MODE=true \
APP_CATALOG_MAX_FEATURED_COUNT=1 \
SERVER_PORT=9095 \
./gradlew bootRun

И вот здесь возникает простая инженерная мысль: «А можно я это передам одним блоком, чтобы оно было атомарно и в одном месте?» Spring Boot отвечает: «Можно. Держи SPRING_APPLICATION_JSON».

2. Что такое SPRING_APPLICATION_JSON

Важно сразу правильно понять идею: SPRING_APPLICATION_JSON — это не «JSON-конфиг вместо YAML». Это способ передать несколько свойств одной строкой, чаще всего через переменную окружения. Spring Boot умеет взять эту строку, распарсить JSON и добавить получившиеся значения в Environment как отдельный источник свойств.

На практике у механизма есть два «входа», которые полезно знать по именам, чтобы не путаться в документации и в чужих примерах:

  • SPRING_APPLICATION_JSON — имя переменной окружения (environment variable), куда вы кладёте JSON.
  • spring.application.json — то же самое по смыслу, но как обычное имя свойства (его можно передать и как -Dspring.application.json=..., и как --spring.application.json=...).

Фокус в том, что Boot не просто читает это значение «как строку». Он превращает его в отдельный property source (условно: «JSON-источник») и размещает его высоко в цепочке приоритетов.

Чтобы было видно, куда JSON-источник встраивается в уже знакомую лестницу, держите простую схему precedence, удобную именно для этой темы:

flowchart TD
    A["command-line args --key=value"] --> B["SPRING_APPLICATION_JSON spring.application.json"]
    B --> C["Java system properties -Dkey=value"]
    C --> D["OS environment variables"]
    D --> E["external config files"]
    E --> F["packaged application.yaml"]

Эта диаграмма не про «все возможные источники на свете», а про практичную карту: где дефолт, где override, и почему одно значение побеждает другое.

3. Расплющивание JSON в ключи

С точки зрения человека JSON — это дерево (вложенные объекты). С точки зрения Environment — это плоские ключи вида app.catalog.title. Поэтому Boot делает понятную вещь: берёт вложенность и расплющивает её в dot-separated ключи.

Например, вот такой JSON:

{
  "app": {
    "catalog": {
      "title": "JSON title",
      "maintenance-mode": true
    }
  }
}

превратится в два обычных свойства:

- app.catalog.title = JSON title
- app.catalog.maintenance-mode = true

Можно смотреть на это как на YAML, только без красивых отступов и без комментариев. (Да, это минус. JSON не терпит комментарии. Он строгий как преподаватель на зачёте — «лишняя запятая? до свидания».)

Хороший способ закрепить понимание — маленькая табличка соответствий:

JSON-фрагмент Во что превращается в Environment
{"server":{"port":9095}}
server.port=9095
{"app":{"catalog":{"title":"Demo"}}}
app.catalog.title=Demo
{"app":{"catalog":{"maintenance-mode":true}}}
app.catalog.maintenance-mode=true

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

4. Передача JSON: env, -D, --

На уровне повседневной работы у SPRING_APPLICATION_JSON есть три «транспорта». Новой precedence-системы тут не появляется: меняется только способ доставки одной и той же строки до Boot. И да, здесь начинается самое весёлое: экранирование кавычек. Считайте это мини-налогом за компактность.

Через env var SPRING_APPLICATION_JSON

В Unix-подобных оболочках обычно проще всего использовать одинарные кавычки снаружи, а внутри JSON оставить двойные:

# Один JSON-блок вместо набора отдельных переменных окружения
SPRING_APPLICATION_JSON='{"app":{"catalog":{"title":"Demo Catalog","maintenance-mode":true}}}' \
./gradlew bootRun

Если у вас Windows PowerShell, подход похожий, только синтаксис присваивания другой:

# PowerShell: задаём переменную окружения и запускаем приложение
$env:SPRING_APPLICATION_JSON = '{"app":{"catalog":{"title":"Demo Catalog","maintenance-mode":true}}}'
./gradlew bootRun

Главная идея: мы передаём одну переменную, но внутри неё сразу несколько связанных настроек.

Через -Dspring.application.json=...

Иногда удобнее передать это как JVM-параметр, особенно если вы запускаете приложение как обычную Java-команду и хотите, чтобы настройка жила именно на уровне JVM (или так проще настроить в IDE).

Пример выглядит так:

# Важно: -D... — это параметр JVM, он должен стоять до -jar
java -Dspring.application.json='{"server":{"port":9095}}' -jar app.jar

Здесь важный момент из серии «ошибка на миллион»: -D... — это параметр JVM, и он должен стоять до -jar. Если поставить после, JVM его не увидит. Она не обидится, она просто проигнорирует — и вы снова будете спорить с реальностью.

Через --spring.application.json=...

Boot умеет принимать это и как аргумент приложения. На вид это вообще выглядит как обычный --key=value, только значение — JSON:

# Gradle: аргументы приложения передаются через --args (и тут обычно начинаются кавычки)
./gradlew bootRun --args='--spring.application.json={"app":{"catalog":{"title":"CLI JSON title"}}}'

Это работает, но чаще всего становится самым «кавычкозависимым» вариантом. Если вы не любите экранировать кавычки и спорить с shell’ом, env var обычно спокойнее.

5. Приоритеты и null

SPRING_APPLICATION_JSON легко переоценить: задали APP_CATALOG_TITLE, не сработало, а потом оказывается, что сверху был JSON-блок. Поэтому здесь полезно зафиксировать не всю карту заново, а только место этого источника: command-line args обычно сильнее, затем идёт JSON-блок, затем system properties и env vars, а потом уже файлы.

Давайте разберём один ключ app.catalog.title в пяти местах, чтобы увидеть поведение глазами:

1) В application.yaml лежит дефолт:

app:
  catalog:
    # Дефолтное значение из файла конфигурации
    title: "Title from file"

2) Вы добавили env var:

APP_CATALOG_TITLE="Title from env"

3) Вы добавили JSON-блок:

SPRING_APPLICATION_JSON='{"app":{"catalog":{"title":"Title from JSON"}}}'

4) И сверху ещё указали command-line:

--app.catalog.title="Title from CLI"

Итоговое значение будет "Title from CLI", потому что аргументы командной строки победят всех.

Теперь важный нюанс про null. Новички часто ожидают, что так можно «сбросить» свойство:

# Важно: null тут не работает как «сброс/стирание», а скорее как «ключа нет»
SPRING_APPLICATION_JSON='{"app":{"catalog":{"title":null}}}'

Ожидание: «ну я же задал null, значит оно пустое». Реальность: для этого механизма null не работает как «стирание». В итоге у вас останется значение из более слабого источника (например, из application.yaml), потому что ключ с null считается по сути отсутствующим. То есть null — это не кнопка Reset.

Если нужно вернуть дефолт, самый надёжный путь — просто не задавать override. Конфигурация в Boot вообще любит простую философию: «если не знаешь, что делать — убери лишнее, и станет понятнее».

6. Мини-диагностика через Environment

Когда начинается конфликт источников, мозг пытается «вспомнить», что вы запускали, что было в терминале, что IDE подсовывает в Run Configuration, и где вообще истинная правда. В этот момент полезнее всего не гадать, а спросить у приложения напрямую: «что ты видишь в Environment?».

Ниже — тот же временный probe-runner, только с ключами, которые удобно проверить именно для JSON-override. Не держите его постоянно в проекте: это просто фонарик, чтобы быстро увидеть, кто победил в precedence-цепочке.

import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

// Учебный хелпер: печатаем итоговые значения из Environment, чтобы понять precedence источников
@Bean
ApplicationRunner configEchoRunner(Environment env) {
    return args -> {
        // getProperty(...) обычно возвращает строку или null, если ключ не задан ни в одном источнике
        System.out.println("app.catalog.title = " + env.getProperty("app.catalog.title"));

        // Здесь ключ с дефисом — это нормально: он был «расплющен» из JSON/YAML в app.catalog.maintenance-mode
        System.out.println("maintenance = " + env.getProperty("app.catalog.maintenance-mode"));

        // Удобный маркер для проверки, какой именно источник переопределил server.port
        System.out.println("server.port = " + env.getProperty("server.port"));
    };
}

Если вы запустите приложение с таким JSON:

SPRING_APPLICATION_JSON='{"app":{"catalog":{"title":"JSON title","maintenance-mode":true}},"server":{"port":9095}}' \
./gradlew bootRun

то вывод будет примерно таким (значения — для примера):

app.catalog.title = JSON title          // app.catalog.title = JSON title
maintenance = true                     // maintenance = true
server.port = 9095                     // server.port = 9095

Смысл здесь в том, что приложение перестаёт быть «чёрным ящиком». Вы больше не спорите с YAML-файлом на диске. Вы спрашиваете у Environment, и он показывает итоговую правду.

7. Типичные ошибки при использовании SPRING_APPLICATION_JSON

Эта тема кажется маленькой и «как будто очевидной», пока вы не словили первый запуск, где JSON не распарсился, кавычки съел shell, а приложение тихо стартовало с дефолтами — и вы уверены, что Boot вас игнорирует из вредности. На самом деле почти все проблемы тут типовые и решаются внимательностью к синтаксису и приоритетам. Давайте разберём самые частые грабли.

Ошибка №1: JSON невалидный (лишняя запятая, неэкранированная кавычка, комментарий).
YAML умеет быть «человечнее» и иногда прощает мелкие огрехи, а JSON — нет. Особенно часто ломают конфиг trailing comma ({"a":1,}) и попытка вставить комментарий. Лечится скучно: проверкой JSON на валидность и привычкой держать его максимально маленьким.

Ошибка №2: shell “съел” кавычки, и до Boot дошла каша.
На Bash и PowerShell правила кавычек разные, на Windows CMD — ещё веселее. Если вы видите, что значение будто не применяется, первое, что стоит сделать — вывести переменную окружения в том же терминале и убедиться, что в ней действительно лежит корректная строка JSON, а не обрезанный фрагмент.

Ошибка №3: попытка хранить большой конфиг одной JSON-строкой.
SPRING_APPLICATION_JSON хорош, когда вы переопределяете 2–5 связанных значений. Когда вы пытаетесь засунуть туда половину application.yaml, у вас пропадает читаемость, растёт риск ошибок, и любые изменения превращаются в «редактирование минированной строки». Для постоянной конфигурации лучше остаются файлы.

Ошибка №4: ожидание, что null “сбросит” значение из файла.
Интуитивно кажется, что null — это «пусто», значит override должен победить. Но для этого механизма null не работает как стирание ключа. В итоге вы думаете, что “обнулили”, а приложение берёт значение снизу по precedence. Если нужно убрать override — убирайте сам override.

Ошибка №5: перепутаны приоритеты источников (особенно CLI args поверх JSON).
Очень частый сценарий: вы задали JSON-блок и уверены, что он главный, а потом в IDE или в команде запуска где-то спрятался --app.catalog.title=.... И он победил. В такие моменты помогает либо печать итоговых значений через Environment, либо просто дисциплина: в конфликтных ситуациях всегда сначала смотрим на фактическую команду запуска.

1
Задача
Spring Boot, 14 уровень, 2 лекция
Недоступна
Несколько свойств через SPRING_APPLICATION_JSON
Несколько свойств через SPRING_APPLICATION_JSON
1
Задача
Spring Boot, 14 уровень, 2 лекция
Недоступна
null в JSON не стирает значение из файла
null в JSON не стирает значение из файла
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ