JavaRush /Курсы /Spring Boot /Запуск jar и запросы

Запуск jar и запросы

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

1. Инструкция запуска для jar

Если вы только что собрали bootJar и гордо произнесли «всё, сервис упакован», это похоже на ситуацию, когда вы купили мебель, но инструкция осталась в коробке… которую вы выбросили. Технически детали у вас есть, но любой следующий человек (и вы же через неделю) будет собирать проект по наитию, а это всегда заканчивается одним и тем же заклинанием: «works on my machine».

Важный момент: инструкция запуска — это не «текст для красоты» и не «документация на потом». Для Spring Boot-проекта это часть воспроизводимости, такая же, как Gradle Wrapper. Она фиксирует очень приземлённые вещи: какой командой собрать, какой командой запустить, какой профиль включить, какие переменные можно переопределять, какой порт ожидать и какими запросами проверить, что сервис жив.

После того как мы разобрали типовые расхождения между IDE и java -jar, становится видно: одной команды запуска мало. Нужен короткий сценарий build → run → check, который другой разработчик сможет повторить без вашей IDE, без устных комментариев и без поисков «той самой настройки» в Run Configuration. Пока такого сценария нет, сервис всё ещё привязан к IDE и плохо готов жить как артефакт, а не как проект в IDEA.

Можно думать о README.md как о маленьком контракте между «автором проекта» и «любым человеком, который пришёл позже». Причём этот «любой человек» очень часто — вы сами, только уставший, без кофе и с чувством, что Spring всё сломал специально именно сегодня (нет, просто вы забыли --spring.profiles.active=local).

Чтобы не превращать эту лекцию в философию, зафиксируем практическую цель: после неё вы должны уметь сделать так, чтобы другой разработчик, получив репозиторий catalog-service, мог выполнить один короткий сценарий build → run → check и получить тот же результат, что и вы.

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

flowchart TD
  %% Минимальный сценарий: собрать → запустить → проверить, что сервис отвечает
  A[Клонировал репозиторий] --> B["Собрал jar: ./gradlew bootJar"]
  B --> C["Запустил: java -jar ... --spring.profiles.active=local"]
  C --> D[Проверил /actuator/health]
  D --> E[Проверил /api/catalog/courses]
  E --> F[Убедился: сервис работает]

2. Что добавить к репозиторию

Когда проект уже умеет запускаться как jar, появляется новый уровень зрелости: проект должен быть самообъясняемым. Это не значит «всё задокументировать как ГОСТ», но значит, что ключевые точки запуска должны быть видны сразу, без археологии по чатам и без поиска «той самой команды» в истории терминала. Для новичка это особенно критично: если человек застрял на запуске, весь остальной код теряет смысл.

Раз уж мы уже увидели, как легко jar расходится с IDE из-за профиля, working directory и внешнего конфига, эти вещи нужно вынести из головы автора в явную инструкцию. Это и есть operational-ответ на все «а у меня работает иначе».

Обычно минимальный набор сопровождающих артефактов в репозитории выглядит так: README.md с командами сборки и запуска, и файл с примерами запросов (часто http/catalog-service.http или просто секция curl в README). Если вы поддерживаете внешние конфиги через ./config/, полезно иметь хотя бы один пример такого файла, чтобы было понятно, как он должен выглядеть и откуда подхватывается.

Чтобы не уходить в списки «а давайте ещё wiki, runbook, ADR и отдельный сайт документации», зафиксируем именно Boot-минимум. Удобно увидеть его в таблице:

Артефакт Где лежит Для чего нужен
README.md корень репозитория Быстрый сценарий «собрать и запустить» + что проверить руками
http/catalog-service.http (или аналог) папка http/ Пара запросов «проверить, что живо» без ручного набора URL
config/catalog-extra.yaml (опционально) папка config/ Пример внешнего override-конфига, чтобы не угадывать формат
src/main/resources/... внутри проекта «Встроенная» конфигурация, которая попадёт внутрь jar

Обратите внимание на методическую мысль: мы не превращаем эту тему в «документирование всего на свете». Нам нужно только то, что делает запуск повторяемым и проверяемым. Всё остальное уже будет приятно, но не обязательно для сегодняшней цели.

3. README.md: коротко и полезно

README обычно ломают двумя противоположными способами. Первый — написать полотно текста на три экрана про «миссию сервиса», но забыть, какой командой его запускать. Второй — написать одну строчку «Run: ./gradlew bootRun», и на этом закончить, хотя сегодня мы как раз учимся запускать jar вне IDE. Нам нужен золотой середняк: минимум текста, максимум «копируй-вставляй».

Хорошая структура README для нашего catalog-service может быть очень простой и повторяемой. В нём почти всегда есть маленький блок про сборку, блок про запуск jar, и блок «как проверить, что сервис жив». Если вы используете профили (local/dev/prod), это надо назвать прямо. Если Actuator по профилям открывается по-разному (а у нас именно так), это тоже нужно уточнить, иначе человек получит 404 и решит, что jar «не работает».

Пример совсем короткого «ядра» README, которое уже спасает нервы:

## Build
./gradlew clean bootJar

## Run (local)
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar --spring.profiles.active=local

## Smoke check
GET http://localhost:8080/actuator/health
GET http://localhost:8080/api/catalog/courses

Заметьте: я специально использую один и тот же versioned jar-path, который фигурировал в командах по остальным лекциям дня. Это убирает скрытый переход на другой snapshot проекта. Если отдельно зафиксируете имя артефакта, README можно будет упростить — это optional improvement, о котором поговорим ниже.

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

После запуска в логах должен появиться порт (обычно 8080) и сообщение о старте приложения.

Это маленькая вещь, но она снижает тревожность. А тревожность — главный враг при изучении Spring (после NoSuchBeanDefinitionException, конечно).

4. Стабильное имя jar — как optional improvement

Одна из самых недооценённых причин хаоса в инструкциях запуска — меняющееся имя артефакта. Сегодня это catalog-service-0.0.1-SNAPSHOT.jar, завтра версия поменялась, послезавтра кто-то переключился на другой archiveBaseName… и README начинает врать. Это не ошибка Spring Boot, это ошибка «мы не договорились, как называется результат сборки».

В учебном проекте почти всегда выгодно сделать имя артефакта стабильным, чтобы команды были одинаковыми. Но текущий день можно пройти и без этой настройки: канонический сценарий пока остаётся с build/libs/catalog-service-0.0.1-SNAPSHOT.jar. Если хочется упростить handoff и убрать версию из каждой команды, можно отдельно зафиксировать имя артефакта.

Минимальный пример в build.gradle.kts:

tasks.named<org.springframework.boot.gradle.tasks.bundling.BootJar>("bootJar") {
    // Делаем имя артефакта стабильным, чтобы README не начинал «врать» при смене версии
    archiveFileName.set("catalog-service.jar")
}

После этого итоговый файл будет называться catalog-service.jar, и уже тогда README можно синхронно перевести на короткую команду запуска. Главное — не смешивать оба варианта в одном документе: либо вы везде пишете versioned jar из build/libs, либо везде переходите на фиксированное имя.

И ещё один тихий плюс: когда вы собираете jar снова и снова, вы не получаете «зоопарк» файлов с разными версиями в build/libs. Один файл — один очевидный кандидат на запуск. Очень по-человечески.

5. Примеры запуска и override

Если в README написать десять способов запуска, он превращается в меню ресторанчика, где надо выбрать «правильное блюдо», иначе сервис не стартанёт. Для «воспроизводимой инструкции» лучше работает другой подход: один базовый запуск, и пара понятных примеров переопределения конфигурации — ровно столько, чтобы увидеть, что externalized configuration реально работает вне IDE.

Базовый запуск обычно выглядит так (и да, мы специально явно задаём профиль):

# Запуск из корня проекта, чтобы пути вроде build/libs/... были однозначными
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.profiles.active=local

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

# Переопределяем порт через параметр — удобно для локального запуска, когда 8080 занят
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.profiles.active=local \
  --server.port=9090

Теперь важный момент: когда вы меняете порт, все примеры запросов тоже должны соответствовать. Это частая ловушка: запускают на 9090, а проверяют http://localhost:8080/actuator/health и потом обвиняют Spring в саботаже.

Третий полезный пример — переопределение одного прикладного свойства через env vars. Он показывает, что jar «живёт» в среде и читается снаружи:

# То же самое, но профиль и одно прикладное свойство задаём через переменные окружения
SPRING_PROFILES_ACTIVE=local \
APP_CATALOG_MAXFEATUREDCOUNT=6 \
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar

Здесь мы держимся того же канонического правила именования, что и в лекции про runtime overrides: точки превращаются в _, дефис из max-featured-count исчезает, всё становится верхним регистром. Поэтому из app.catalog.max-featured-count получается APP_CATALOG_MAXFEATUREDCOUNT.

И последний нюанс, который обязательно стоит проговорить текстом в README: команды с --spring.config.additional-location=... зависят от текущей директории процесса. Поэтому хороший тон — запускать jar из корня проекта (или явно написать, откуда запускать), чтобы ./config/ означало то, что вы думаете.

6. Примеры запросов и smoke-check

Проверять сервис «на глаз» по логам полезно, но недостаточно. Логи могут сказать «Started», а вы всё равно не уверены, что endpoints доступны и что каталог реально загрузился. Поэтому второе обязательное дополнение к README — пара запросов, которые подтверждают, что сервис работает как сервис, то есть отвечает по HTTP.

Для catalog-service удобнее всего иметь файл http/catalog-service.http (формат понимают IntelliJ IDEA, VS Code с расширениями и многие другие инструменты). В нём можно держать короткие запросы, которые выполняются буквально одной кнопкой.

Пример такого файла (кусочком, чтобы не превращать лекцию в справочник):

### Health (Actuator)
# Быстрая проверка, что процесс поднялся и отвечает по HTTP
GET http://localhost:8080/actuator/health

### Courses
# Проверка, что основной endpoint отдаёт данные (а не только "приложение стартовало")
GET http://localhost:8080/api/catalog/courses

Если хочется сразу показать «живой фильтр» (и заодно убедиться, что query params работают), можно добавить ещё один запрос:

### Featured only, limit 2
# Проверяем query params: фильтр + лимит
GET http://localhost:8080/api/catalog/courses?featuredOnly=true&limit=2

Для людей, которые не пользуются .http файлами, полезно продублировать в README 1–2 команды curl. Тут важно не усложнять: curl должен сработать «из коробки», без обязательного jq и без хитрых пайпов.

# Минимальный вариант проверки без дополнительных утилит
curl http://localhost:8080/actuator/health

Если вывод слишком длинный, это не трагедия. Мы сейчас не строим «красивую консоль», мы строим уверенность, что сервис отвечает.

Чтобы студенту было проще понимать, зачем эти запросы именно такие, можно добавить небольшую табличку прямо в README (или хотя бы у себя в голове держать её при написании):

Что проверяем Какой запрос Что ожидаем увидеть
Процесс жив и отвечает GET /actuator/health "status":"UP" (или аналогичный статус)
Каталог отдаётся GET /api/catalog/courses JSON-массив курсов
«featured» работает GET /api/catalog/featured список ограниченного размера
Получение по slug GET /api/catalog/courses/{slug} один объект курса

Тут важная методическая граница: мы не превращаем README в документацию API на 40 страниц. Нам достаточно «smoke-check набора», который доказывает, что приложение поднялось, и основная функциональность проекта жива.

7. Внешний конфиг в ./config/

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

Например, можно положить config/catalog-extra.yaml с парой переопределений. Пусть он будет коротким и очевидным:

app:
  catalog:
    # Удобно сразу увидеть, что override реально применился (по логам или по ответам)
    title: "Catalog Service (external override)"
    # Специально маленькое число, чтобы эффект от конфига был заметен в выдаче
    max-featured-count: 2

И в README показать команду, которая подхватывает эту директорию как дополнительную локацию (именно additional, чтобы не убить стандартный поиск конфигов):

# optional: — чтобы запуск не падал, если папки ./config/ нет (это пример, а не обязательное условие)
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar \
  --spring.profiles.active=local \
  --spring.config.additional-location=optional:file:./config/

Здесь сразу несколько важных смыслов. Во-первых, optional: защищает нас от падения, если директории нет (мы хотим «пример», а не «обязаловку»). Во-вторых, file:./config/ завязан на текущую директорию процесса, поэтому запускать стоит из корня проекта. В-третьих, такой пример учит правильной привычке: jar не трогаем, конфигурацию меняем снаружи.

Если вы захотите чуть повысить ясность для новичка, можно добавить в README одну строку пояснения человеческим языком: «Если положить файл в ./config/, он переопределит значения из application.yaml». На этом всё — не надо превращать README в отдельный курс по precedence, мы это уже сделали в модуле про конфигурацию.

8. Типичные ошибки при handoff

Ошибка №1: README описывает запуск из IDE.
Это очень частый перекос: проект уже умеет запускаться как артефакт, но документация осталась на уровне «нажмите зелёный треугольник». В результате следующий человек повторяет запуск, но он проверяет не то, что вы хотели проверить. Для packaging-истории важно, чтобы в README была команда java -jar с явным путём к артефакту.

Ошибка №2: в README указан несуществующий jar, потому что имя артефакта поменялось.
Сегодня файл называется catalog-service-0.0.1-SNAPSHOT.jar, завтра вы поменяли версию, и команды в README перестали работать. Эта ошибка особенно болезненна для новичка: он ещё не знает, где искать jar, и воспринимает это как «Gradle сломан». Лечится либо стабильным именем через archiveFileName, либо железной дисциплиной обновлять команды при изменении артефакта.

Ошибка №3: sample requests написаны под один порт, а запуск в инструкции — под другой.
Это выглядит мелко, но ломает доверие к документу моментально. Человек запускает на 9090, а запросы стоят на 8080, получает connection refused и начинает «чинить Spring». Хорошая инструкция либо держит один порт, либо явно пишет: «если меняете порт, меняйте базовый URL в запросах».

Ошибка №4: не учтено, что Actuator endpoints открываются по профилям.
Если в prod открыт только health, а вы в README советуете сходить на /actuator/env, то человек решит, что Actuator «не работает». В README нужно либо проверять то, что гарантированно доступно (обычно health), либо честно уточнить: «в local/dev доступно больше».

Ошибка №5: внешняя конфигурация описана относительным путём без упоминания текущей директории процесса.
Команда --spring.config.additional-location=optional:file:./config/ работает только если вы запускаете jar из правильной папки. Если человек запускает jar из другой директории, ./config/ становится «не тем ./config». Для новичка это выглядит как магический баг. Лечится простой фразой в README: «запускайте из корня проекта» или «используйте абсолютный путь».

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