JavaRush /Курсы /Spring Boot /Запуск jar: профили и конфиги

Запуск jar: профили и конфиги

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

1. Конфигурация после сборки jar

Когда приложение живёт в IDE, мозг быстро привыкает к иллюзии, что «проект = приложение». Внутри IDE рядом лежит папка src/main/resources, профили включаются галочкой в Run Configuration, а нужные параметры могут случайно сохраниться в настройках запуска. Но jar-файл — это уже «чемодан с вещами»: вы его отдали — и всё, дальше у вас нет права подложить туда ещё один носок незаметно.

И тут внезапно выясняется, что конфигурация — это не декоративный YAML «для красоты», а часть контракта запуска. Для Boot-сервиса нормальная модель такая: один и тот же jar запускается в разных средах (local/dev/prod), а различия задаются профилями, переменными окружения, аргументами командной строки и внешними конфигами. Если вы это умеете — вы реально умеете Spring Boot. Если нет — вы умеете нажимать зелёный треугольник (и это тоже навык, просто… чуть менее монетизируемый).

В этой лекции будем считать, что jar уже собран и лежит, например, здесь:

build/libs/catalog-service-0.0.1-SNAPSHOT.jar

Если у вас имя другое — это нормально: главное, чтобы вы запускали тот файл, который вы только что собрали.

2. Откуда Spring Boot берёт свойства при запуске jar

Перед тем как переопределять профили и свойства, полезно навести порядок в голове: Boot сначала собирает набор источников свойств (property sources), а потом применяет правило приоритета. Мы эту модель уже обсуждали в модуле про externalized configuration, но сейчас она становится особенно практической: вы буквально «крутите ручки», а приложение либо слушается, либо нет.

Ниже — упрощённая карта источников, без попытки превратить лекцию в справочник. Важно понять принцип: в этой схеме приоритет растёт сверху вниз. То, что стоит ниже, сильнее override'ит то, что было задано выше. SPRING_APPLICATION_JSON пока не включаем в диаграмму, потому что отдельно разберём его чуть ниже.

flowchart TB
  A["Низкий приоритет: внутри jar — application.yaml и application-{profile}.yaml"] --> B["Внешние config files: ./config, дополнительные location/import"]
  B --> C["Environment variables"]
  C --> D["Java system properties -D..."]
  D --> E["Высокий приоритет: command-line args --key=value"]
  E --> F["Итоговая конфигурация в Environment"]

Самая частая неожиданность у новичка в packaged-run: он меняет application.yaml в проекте и ожидает, что это влияет на уже собранный jar. Но jar — это «фото на паспорт»: оно не меняется от того, что вы постриглись. Если вы правите ресурсы — jar надо пересобрать. Если хотите менять поведение без пересборки — используйте runtime overrides (env vars, -D, --..., внешние конфиги).

Для catalog-service (по ТЗ курса) базовые конфиги обычно такие:

application.yaml — общий.
application-local.yaml, application-dev.yaml, application-prod.yaml — профильные.
catalog-data.yaml — импортируемый файл со списком курсов (через spring.config.import).

И всё это может жить внутри jar, а поверх — у вас могут быть внешние файлы (например, ./config/catalog-extra.yaml) или вообще отдельная папка конфигурации, которая лежит рядом с jar на сервере.

3. Включаем профиль при java -jar: CLI args, env vars и -D

Профиль — это не «режим работы приложения для красоты», а способ включить нужную конфигурацию и (иногда) нужные бины. В нашем курсе профили — один из ключевых инструментов: local для удобной разработки, dev для более «реалистичной» диагностики, prod для минимально безопасного exposure и более строгих настроек.

Технически активировать профиль можно несколькими путями. Отличие не в магии Spring, а в том, как именно вы передаёте параметр процессу. И тут полезно мыслить так: «мне нужен активный профиль — я выбираю канал доставки».

Самый прямой и наглядный способ — аргумент командной строки:

java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.profiles.active=dev

Если вы любите переменные окружения (а они обычно отлично вписываются в запуск сервисов), то это будет так:

SPRING_PROFILES_ACTIVE=prod \
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar

Третий вариант — Java system properties через -D. Это чуть «олдскульнее», но по-прежнему рабочий и распространённый путь:

java -Dspring.profiles.active=local \
  -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar

Что выбрать? В учебном проекте — любой, лишь бы вы могли воспроизвести запуск без IDE. В реальной жизни часто побеждают env vars (они хорошо дружат с процессными менеджерами) или CLI args (они максимально явные). Главное — не держать профиль «только в IDE», потому что тогда jar-запуск превращается в лотерею.

4. Переопределяем прикладные свойства при запуске jar: --, env vars и -D

Профиль — это только начало. Настоящая сила externalized configuration — в том, что вы можете менять поведение приложения точечно: порт, флаги поведения, лимиты, диагностические настройки. И всё это — без пересборки jar, просто за счёт параметров запуска.

CLI args: переопределяем прямо в запуске

CLI args в Boot выглядят как --key=value. Для jar-запуска это особенно удобно, потому что у вас всегда есть одна строка, которую можно скопировать, сохранить в README или передать коллеге.

Например, запустим catalog-service в профиле dev и на другом порту:

java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.profiles.active=dev \
  --server.port=9090

Теперь добавим переопределение прикладного свойства. По ТЗ у нас есть namespace app.catalog.*, и, допустим, мы хотим увеличить лимит featured-курсов:

java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.profiles.active=dev \
  --app.catalog.max-featured-count=6

Если у вас в коде есть поведение, завязанное на maintenance-mode, можно включить его ровно так же:

java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.profiles.active=local \
  --app.catalog.maintenance-mode=true

Обратите внимание на маленькую, но важную вещь: мы передаём свойства в canonical форме, той же, что в YAML. Boot дальше сам разберётся с naming conventions и binding.

Env vars: переопределяем через окружение

Переменные окружения — классный способ, когда строка запуска не должна раздуваться или когда вы запускаете сервис через какой-нибудь менеджер, который удобно подставляет env vars.

Для server.port это выглядит так:

SPRING_PROFILES_ACTIVE=dev \
SERVER_PORT=9090 \
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar

А вот для app.catalog.max-featured-count важно сразу держать одну каноническую форму, чтобы не плодить конкурирующие варианты именования. В env vars Boot опирается на canonical property name: точки превращаются в _, дефисы из kebab-case убираются, всё переводится в верхний регистр. Поэтому app.catalog.max-featured-count превращается в APP_CATALOG_MAXFEATUREDCOUNT.

SPRING_PROFILES_ACTIVE=dev \
APP_CATALOG_MAXFEATUREDCOUNT=6 \
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar

Небольшая табличка-шпаргалка (именно для наших свойств проекта):

Canonical property (YAML/CLI) Пример env var
spring.profiles.active SPRING_PROFILES_ACTIVE
server.port SERVER_PORT
app.catalog.title APP_CATALOG_TITLE
app.catalog.max-featured-count APP_CATALOG_MAXFEATUREDCOUNT
app.catalog.maintenance-mode APP_CATALOG_MAINTENANCEMODE

System properties -D: переопределяем в JVM

System properties полезны, когда вы управляете запуском JVM через одну «JVM-строку». Например:

# В этом варианте все overrides передаются как свойства JVM
java \
  -Dspring.profiles.active=prod \
  -Dserver.port=8085 \
  -Dapp.catalog.max-featured-count=4 \
  -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar

Это не лучше и не хуже, чем env vars или CLI args — просто другой канал доставки. Но помнить про него полезно: вы будете видеть -D... в реальных проектах довольно часто.

5. SPRING_APPLICATION_JSON

Иногда хочется передать не одно свойство, а сразу набор, и делать для этого десять env vars — грустно. В такие моменты Spring Boot даёт механизм SPRING_APPLICATION_JSON: вы передаёте JSON-строку, внутри которой лежит «кусок конфигурации», и Boot воспринимает это как ещё один property source.

Это похоже на ситуацию «вот вам мини-application.yaml, только в JSON-виде». Пример (обратите внимание: JSON-кавычки — ваш новый источник приключений, особенно в разных оболочках):

# Один env var, внутри которого «кусок конфигурации»
# Важно: кавычки/экранирование зависят от вашей оболочки (bash/zsh/powershell и т.д.)
SPRING_APPLICATION_JSON='{
  "spring": { "profiles": { "active": "dev" } },
  "server": { "port": 9090 },
  "app": { "catalog": { "max-featured-count": 6 } }
}' \
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar

Смысл здесь простой: мы одной переменной окружения включили профиль, поменяли порт и переопределили лимит featured-курсов.

Почему этот механизм полезен именно в контексте packaged-run? Потому что вы начинаете мыслить как инженер, который «перевозит» приложение между окружениями: jar один, настройки меняются снаружи. SPRING_APPLICATION_JSON — один из способов положить часть настроек «рядом с запуском», не создавая отдельный файл.

И да, если вы сейчас подумали «звучит круто, но я точно где-то забуду кавычку», — это абсолютно нормальное чувство. Даже опытные разработчики иногда ловят такие ошибки, потому что JSON в переменной окружения — это как собрать IKEA без инструкции, но с уверенностью в себе.

6. Внешние конфиги: additional-location и location

До этого момента мы меняли поведение через параметры запуска (env vars, -D, CLI args). Но часто есть ещё более «операторский» сценарий: конфиг должен жить в отдельном файле, рядом с jar или в отдельной директории, чтобы его можно было версионировать, менять, подкладывать в разные машины, не трогая сборку.

В нашем проекте по ТЗ даже предусмотрена папка ./config/ и опциональный внешний файл ./config/catalog-extra.yaml. Теперь вопрос: как сказать Boot «посмотри ещё и туда»?

spring.config.additional-location: аккуратно расширяем стандартный поиск

spring.config.additional-location добавляет ещё одну локацию к стандартному поиску. Это значит: Boot как обычно прочитает конфиги из jar и стандартных внешних мест, а потом дополнительно заглянет в указанную локацию.

java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.profiles.active=dev \
  --spring.config.additional-location=optional:file:./config/

Здесь важны три детали.

Во-первых, file: — это явный указатель, что мы читаем из файловой системы, а не из classpath.

Во-вторых, ./config/ — относительный путь. Он будет разрешаться относительно текущей директории процесса, а не относительно расположения jar. Это мы ещё отдельно разберём ниже, потому что это один из самых частых «почему оно не работает?!».

В-третьих, optional: означает «если папки/файлов нет — не падай». Это полезно, когда внешний конфиг не обязателен. В учебном проекте это часто удобно: можно запускать и без ./config, и с ней.

Как Boot будет искать файлы в этой директории? Обычно он ищет стандартные имена вроде application.yaml, application-dev.yaml и т.п. То есть если вы положите в ./config/ файл application-dev.yaml, он может переопределить то, что лежит внутри jar, не требуя пересборки.

spring.config.location: полностью заменяем стандартный поиск

spring.config.location — это другой инструмент. Он не «добавляет ещё одно место», а заменяет стандартный поиск конфигурации на то, что вы указали. Это сильное и иногда опасное действие: можно случайно «отключить» загрузку внутренних ресурсов, к которым вы привыкли.

java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.config.location=file:./config/

Когда это уместно? Когда вы хотите жёстко сказать: «Запуск без внешнего конфига запрещён». Это бывает в реальных системах, где конфиг обязан приезжать отдельно (например, вместе с секретами), и запуск «на дефолтах» считается ошибкой.

Но в учебном проекте и в большинстве первых сервисов я бы начинал именно с additional-location, потому что он мягче и проще для дебага: вы добавили внешний файл — увидели эффект; убрали — вернулись к конфигу внутри jar.

7. Проверяем, что профиль и переопределения применились

Когда конфигурация задаётся «снаружи», всегда есть риск самообмана: вы уверены, что передали нужный параметр, а приложение продолжает жить по старому. И здесь важно не гадать, а проверять. К счастью, мы уже построили для catalog-service хороший diagnostic baseline: логи и Actuator.

Самый простой и «бедный, но честный» способ — логировать важные параметры при старте. В нашем проекте для этого и существует StartupSummaryRunner: он должен сообщать, с какими профилями и какими ключевыми настройками стартанул сервис. Это особенно важно в jar-режиме, потому что там IDE больше не подсвечивает вам активный профиль красивой галочкой.

Небольшой пример того, как может выглядеть фрагмент startup-лога (идея, а не единственный верный формат):

Active profiles: dev
Server port: 9090
Catalog title: Spring+ Catalog
Max featured count: 6

Если вы хотите сделать этот лог более надёжным и привязать его к нашей typed configuration, вот компактный пример (не «полная версия класса», а кусок, который показывает мысль). Обратите внимание на imports: я специально пишу их явно, чтобы вы видели, откуда берутся типы.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component // Регистрируем класс как bean, чтобы он создался при старте контекста
public class StartupConfigLog {

    private static final Logger log = LoggerFactory.getLogger(StartupConfigLog.class);

    public StartupConfigLog(CatalogProperties props, Environment env) {
        // Environment — «финальный» слой, где видны все источники свойств с учётом приоритетов
        log.info("Active profiles: {}", String.join(",", env.getActiveProfiles()));

        // Typed configuration (CatalogProperties) показывает, что биндинг реально сработал
        log.info("Max featured count: {}", props.maxFeaturedCount()); // например: 6
    }
}

Да, этот пример слегка «читерский»: он логирует в конструкторе. В реальном коде вы бы делали это через ApplicationRunner, чтобы логировать уже после поднятия контекста в предсказуемой точке lifecycle (это мы делали раньше). Но как иллюстрация связи Environment + CatalogProperties + лог он хорошо работает и занимает ровно столько строк, сколько нужно, чтобы не утонуть.

Второй способ — Actuator. Если в local/dev профиле у вас открыт /actuator/env и /actuator/configprops, вы можете проверить фактическое значение прямо из работающего процесса. Например (условный запрос, формат зависит от того, как вы смотрите endpoint — через браузер, curl или HTTP-файл):

GET http://localhost:9090/actuator/env
GET http://localhost:9090/actuator/configprops

Смысл этих проверок простой: если вы передали --app.catalog.max-featured-count=6, то вы должны увидеть, что в Environment действительно финальное значение равно 6, и что CatalogProperties тоже связано с этим значением.

8. Относительные пути и working directory процесса

Есть одна коварная штука, которая ломает запуск внешних конфигов чаще, чем Spring, Gradle и космическая радиация вместе взятые. Это текущая директория процесса (working directory). Когда вы пишете file:./config/, вы на самом деле говорите: «возьми папку config относительно того места, откуда я запустил команду».

А место, откуда вы запускаете команду, может быть разным. Например, вы можете:

- находиться в корне проекта и запускать java -jar build/libs/...;
- находиться внутри build/libs и запускать java -jar catalog-service-...;
- запускать jar вообще из другой папки, куда вы его скопировали.

И во всех трёх случаях ./config/ — это разные пути.

Пример, который выглядит одинаково «по смыслу», но отличается по working directory:

# Вариант 1: запускаем из корня проекта
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.config.additional-location=optional:file:./config/
# Вариант 2: сначала зашли в build/libs, и теперь ./config смотрит уже туда
cd build/libs
java -jar catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.config.additional-location=optional:file:./config/

Во втором варианте Boot будет искать build/libs/config, а не <project-root>/config. Если вы об этом не думали — вы будете уверены, что «Spring Boot сломался». А он просто честно делает то, что вы написали.

Как лечится? Самый простой подход — запускать процесс из «стабильной» директории (там, где вы ожидаете конфиг), либо задавать путь явно (например, абсолютный), либо держать внешний конфиг рядом с jar и запускать из этой же папки.

9. Типичные ошибки при запуске jar

Ошибка №1: запускать jar без профиля и удивляться, почему “в dev у меня было иначе”.
Когда вы запускали приложение из IDE, профиль мог быть включён настройкой IDE или переменной окружения, которую IDE подставляет. В jar-запуске этого «сервиса по угадыванию ваших мыслей» уже нет. Поэтому вы запускаете без профиля и получаете дефолтное поведение. Лечится просто: профиль должен быть частью команды запуска или окружения, а не частью памяти IDE.

Ошибка №2: менять application.yaml в проекте и не пересобирать jar.
Это классика жанра. Вы правите ресурсы, запускаете старый jar и думаете, что изменения «не работают». Но jar — это отдельный файл, он не смотрит на вашу папку src. Если вы хотите, чтобы изменения внутри ресурсов попали в артефакт, нужно собрать новый jar. Если вы хотите менять поведение без сборки — используйте runtime overrides или внешний конфиг.

Ошибка №3: путать spring.config.additional-location и spring.config.location.
additional-location расширяет поиск и обычно «прощает» отсутствие внешних файлов, особенно с optional:. location заменяет стандартный поиск и может внезапно отключить часть привычного конфигурационного поведения. Если вы не уверены, что вам нужен жёсткий запрет запуска без внешнего конфига — начинайте с additional-location.

Ошибка №4: неверно назвать env var для прикладного свойства.
Когда вы переходите от --app.catalog.max-featured-count=6 к env vars, легко ошибиться в имени и получить ощущение «переменная есть, а свойство не применилось». Помогает дисциплина: держитесь одной канонической формы вроде APP_CATALOG_MAXFEATUREDCOUNT, а затем проверяйте фактическое значение через startup-логи или Actuator (env/configprops).

Ошибка №5: не учитывать текущую директорию процесса и терять внешний конфиг.
file:./config/ — это не «рядом с jar», а «рядом с working directory». Если вы запускаете jar из другой папки, относительный путь меняет смысл. Это не баг Spring Boot и не повод писать “ну тогда я захардкожу путь в коде” (пожалуйста, не надо). Это повод сделать запуск воспроизводимым и осознанным: либо фиксировать директорию запуска, либо указывать путь явно.

1
Задача
Spring Boot, 24 уровень, 2 лекция
Недоступна
Профиль и env vars для jar-запуска
Профиль и env vars для jar-запуска
1
Задача
Spring Boot, 24 уровень, 2 лекция
Недоступна
Внешняя директория конфигурации для packaged run
Внешняя директория конфигурации для packaged run
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ