JavaRush /Курси /Spring Boot /Три режими запуску Spring Boot‑застосунку

Три режими запуску Spring Boot‑застосунку

Spring Boot
Рівень 24 , Лекція 0
Відкрита

1. Різні режими запуску

Коли застосунок «запускається», у початківця часто спрацьовує внутрішня позначка: «Ну все, працює». Проблема в тому, що способів запуску насправді кілька, і кожен перевіряє своє. Запуск з IDE — це затишний плед, bootRun — уже чесніша перевірка, а java -jar — холодний душ: він не зважає на ваші налаштування в IntelliJ і не підлаштовується під очікування.

У реальному житті Spring Boot‑сервіс майже ніколи не стартує так, як ви запускаєте його в перші тижні навчання. На сервері немає кнопки Run, а в колеги на ноутбуці — ваших «Run Configuration», де ви акуратно виставили профілі, порт і змінні середовища. Тому сьогодні нам потрібно розкласти все по поличках: що саме ми запускаємо і які перевірки проходить застосунок у кожному режимі.

Якщо говорити чесно, одна з найтиповіших професійних ситуацій звучить так: «У мене в IDE працює, а в колеги впало». І майже завжди це не «магія Spring», а просто різні режими запуску та різні припущення, сховані в проєкті.

Терміни та форми запуску

Коли ми говоримо «запуск застосунку», ми часто не уточнюємо, що саме запускається: набір .class-файлів у каталозі чи один зібраний .jar. Саме через цю нечіткість і виникають помилки рівня «я вніс зміни в код, а java -jar поводиться по-старому». Давайте домовимося про терміни, щоб далі говорити не жестами, а словами.

Режим запуску — це поєднання двох речей: як ви запускаєте процес і звідки процес бере класи/ресурси/залежності.

Артефакт збирання — це підсумковий результат процесу збирання. Для нас у цьому модулі (і взагалі в курсі) головним артефактом буде .jar, який лежить у build/libs.

Упакований застосунок (packaged application) — це застосунок у вигляді готового файла-артефакта, який можна винести з проєкту і запустити без IDE.

Розгорнута форма (exploded form) — це запуск застосунку не з одного jar, а з «розсипу» каталогів: окремо скомпільовані класи, окремо ресурси, окремо залежності на classpath.

Є ще два терміни, які спливуть уже сьогодні, навіть якщо ми не будемо їх розбирати скальпелем.

Launcher — це вбудований механізм у Boot-jar, який допомагає запускати застосунок як «один файл». Деталей сьогодні не розбираємо; нам достатньо розуміти, що Boot-jar — це не «звичайний jar із Java Core», а jar, розрахований на запуск.

Поточний каталог процесу — це папка, відносно якої розв’язуються відносні шляхи. У Java його можна побачити через System.getProperty("user.dir"). І так, він раптово стає важливим, коли ви запускаєте jar не з кореня проєкту.

2. Три режими: IDE, bootRun, java -jar

Один і той самий catalog-service справді можна запустити трьома способами — і це не три застосунки, а три різні життєві ситуації. Якщо провести аналогію, то IDE-запуск — це коли ви готуєте вдома на своїй кухні, bootRun — коли дотримуєтеся рецепта з книжки й перевіряєте, що інгредієнти справді на місці, а java -jar — коли ви віддаєте страву в доставку і перевіряєте, чи доїде вона цілою.

Нижче — компактна таблиця, яка фіксує головну ідею: режими відрізняються не логікою catalog-service, а тим, як побудовано classpath, де лежать ресурси і хто підмішав вам параметри запуску.

Режим Чим запускаємо Звідки беруться класи та ресурси Головна перевага Що НЕ перевіряє
IDE Run IDE (Run/Debug) зазвичай build/classes + build/resources, але з особливостями IDE супершвидко і зручно для розробки готовність артефакта і запуск як одного файла
bootRun Gradle task exploded form: каталоги збирання + залежності на classpath відтворювано і ближче до реального збирання упакування застосунку в jar
java -jar JVM напряму packaged application: один jar-артефакт максимально наближено до реальної експлуатації комфорт IDE та її підказки

Щоб картина стала зовсім чіткою, корисно побачити просту схему:

flowchart TD
    A["Вихідні файли: src/main/java та src/main/resources"] --> B["Компіляція та збирання класів/ресурсів"]
    B --> C["Exploded form: build/classes + build/resources"]
    C --> D1["Запуск з IDE"]
    C --> D2["./gradlew bootRun"]
    C --> E["Збирання jar-артефакта"]
    E --> F["java -jar build/libs/...jar"]

Зверніть увагу: IDE Run і bootRun — це про запуск з каталогів, а java -jar — це запуск готового файла. І саме тому java -jar іноді стає тим самим «неприємним, але чесним другом», який першим помічає ваші приховані припущення.

Запуск з IDE

Запуск з IDE (IntelliJ IDEA, Eclipse тощо) — це те, з чого майже всі починають, і це нормально. IDE уміє швидко перебудовувати проєкт, зручно підключати відлагоджувач, показувати логи, підсвічувати помилки конфігурації ще до запуску. Але в IDE є побічний ефект: вона часто робить за вас трохи більше, ніж ви усвідомлюєте, і ви починаєте думати, що так буде всюди.

Найважливіший ризик IDE-запуску — приховані налаштування, які живуть не в репозиторії, а у вас у голові або в IDE. Наприклад, ви могли один раз поставити SPRING_PROFILES_ACTIVE=local у Run Configuration, і тепер завжди запускаєте з локальним профілем, навіть не помічаючи цього. Або IDE виставила робочий каталог у корінь проєкту, і відносні шляхи раптово «працюють». А потім ви запускаєте jar з іншої папки — і починається.

Точка входу при цьому залишається звичайною і передбачуваною — і це добре: у нас як і раніше є main(), і в ньому як і раніше SpringApplication.run(...).

package com.example.catalogservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication // Головна анотація Spring Boot: вмикає автоконфігурацію та сканування компонентів
public class CatalogServiceApplication {

    public static void main(String[] args) {
        // Єдина «офіційна» точка входу в усіх режимах запуску (IDE / bootRun / java -jar)
        SpringApplication.run(CatalogServiceApplication.class, args);
    }
}

Якщо ви бачите тут «магічний рядок, який запускає всесвіт», це нормальна стадія. Але важлива думка дня: магія не в main(). Сюрпризи зазвичай ховаються в середовищі запуску: хто сформував classpath, які args прилетіли, який профіль активний, звідки читаються ресурси.

bootRun

bootRun — це запуск застосунку через Gradle, тобто через той самий інженерний механізм, який керує залежностями, компіляцією та завданнями збирання. Він і далі запускає застосунок у розгорнутій формі (не з jar), але робить це більш відтворювано: у вас з’являється відчуття, що якщо команда в терміналі працює, то з високою ймовірністю спрацює і в колеги.

Типова команда виглядає так:

# Запуск застосунку через Gradle (у розгорнутій формі)
./gradlew bootRun

Якщо ви на Windows, то зазвичай це буде:

# Те саме, але через bat-обгортку для Windows
gradlew.bat bootRun

Що важливо розуміти на рівні здорового глузду: bootRun запускає те, що Gradle вважає вашим застосунком. Якщо у вас залежність не підтягнулася, якщо зламався кеш Gradle, якщо ви забули синхронізувати проєкт — bootRun частіше покаже проблему раніше, ніж IDE-запуск (бо IDE іноді «живе своїм життям» і може тримати старий стан проєкту).

А ще bootRun корисний тим, що він не прив’язаний до кнопки й інтерфейсу IDE. Це означає, що ви можете швидко виробити звичку: «Будь-яка фіча має стартувати не тільки в IDE, а й із термінала». Ця звичка не робить вас «олдскульним консольним магом» — вона просто зменшує кількість сюрпризів, коли проєкт переїжджає з вашої машини.

Якщо потрібно передати аргументи застосунку, Gradle робить це через --args. Це частий момент, де новачки починають сперечатися з лапками, а лапки перемагають.

# Важливо: це аргументи застосунку, а не аргументи Gradle
./gradlew bootRun --args="--server.port=8085"

Тут сенс простий: --args="..." — це аргументи вашого застосунку, а не Gradle. І так, якщо у вас в аргументах кілька параметрів, усі вони мають потрапити всередину цих лапок, інакше Gradle почне думати, що ви намагаєтеся керувати ним самим.

java -jar

Запуск через java -jar — це момент істини. Ви більше не запускаєте «проєкт», ви запускаєте файл. І цей файл має містити все, що потрібно для старту: класи застосунку, ресурси та спосіб коректно підняти середовище виконання Spring Boot. На цьому кроці IDE уже не може «допомогти» — і в цьому якраз цінність.

Команда виглядає максимально по-шкільному:

# Запуск уже зібраного артефакта: тут важливий конкретний jar-файл у build/libs
java -jar build/libs/catalog-service-0.0.1-SNAPSHOT.jar

Якщо ви відчуваєте легку тривогу через назву файла — це нормально. У jar-файла часто є версія, суфікси та інші радощі життя. У реальній роботі перша дія — не вгадувати, а подивитися, що лежить у build/libs. Важливо запам’ятати лише принцип: java -jar запускає те, що ви зібрали, а не те, що у вас зараз відкрите в IDE.

Психологічно корисно сприймати java -jar як «передачу застосунку іншій людині». Ви ніби кажете: «Ось тобі один файл, запусти його, будь ласка». Якщо для запуску потрібно щось іще — наприклад, відкрити IntelliJ, виставити робочий каталог або мати саме такий профіль — значить, процес поки що не відтворюваний.

І так, саме тут спливають речі на кшталт «я читав файл із src/main/resources/...». Бо поза проєктом каталогу src/ просто не існує. У jar є свій внутрішній світ, і він не зобов’язаний повторювати вашу структуру репозиторію.

3. Аргументи та перевірки запуску

Одна точка входу: args доходять усюди

Дуже важливий заспокійливий факт: у всіх трьох режимах запуску точка входу залишається тією самою. Не існує окремого «Spring Boot режиму», де запускається інший main(). Запуск — це завжди виклик CatalogServiceApplication.main(args), а далі Boot розгортає контекст і піднімає вбудований сервер.

Щоб відчути це руками, зручно зробити мікроскопічний лог на старті: вивести, які аргументи реально побачив застосунок, і з якого каталогу він стартував. Це корисно саме сьогодні, тому що args і user.dir — два параметри, які часто «випадково різні» між IDE та java -jar.

Нижче — приклад невеликого ApplicationRunner. Його логіка не має перетворюватися на бізнес-фічу; це радше «пломба», яка показує: аргументи справді доходять.

package com.example.catalogservice.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // Конфігураційний клас: тут оголошуємо інфраструктурні біни
public class StartupArgsConfiguration {

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

    @Bean
    ApplicationRunner startupArgsReporter() {
        // ApplicationRunner виконується після підняття контексту і перед тим, як застосунок «стане на ноги»
        return args -> {
            // Показуємо, які опції реально побачив Spring Boot (корисно порівнювати IDE vs java -jar)
            log.info("Назви опцій: {}", args.getOptionNames());       // наприклад: [server.port]

            // Поточний каталог процесу впливає на відносні шляхи
            log.info("user.dir: {}", System.getProperty("user.dir"));  // наприклад: /Users/alex/projects/catalog-service
        };
    }
}

Зверніть увагу на дві речі. По-перше, ми записуємо це в лог через Logger, бо ми вже дорослі й проходили logging (так, це тонкий натяк на дисципліну). По-друге, user.dir виглядає настільки нудно, що його легко недооцінити — доки ви не запустите jar з іншої папки і не зрозумієте, чому відносний шлях раптово «шукається не там».

Три запуски як три перевірки

Розділяти режими запуску корисно не заради теорії, а заради простої інженерної поведінки: ви починаєте розуміти, який запуск що перевіряє, і перестаєте вимагати від одного режиму того, що він не зобов’язаний перевіряти. IDE-запуск чудовий для розробки та відлагодження, bootRun зручний для відтворюваної перевірки «проєкт живий як Gradle-проєкт», а java -jar перевіряє «застосунок живе як артефакт».

Є зручний спосіб тримати це в голові: не намагайтеся вибрати «єдино правильний режим», а використовуйте режими за призначенням. Коли ви пишете код і хочете швидко побачити результат — IDE ідеальна. Коли вам важливо переконатися, що проєкт чесно збирається і запускається через інструмент збирання — bootRun робить це без зайвих ритуалів. Коли ви хочете переконатися, що застосунок можна винести з репозиторію і запустити як сервіс — java -jar перетворює це на перевірюваний факт, а не на оптимістичну надію.

Якщо сприймати ці три запуски як різні «рівні перевірки», зникає відчуття, що jar-запуск «вередує». Він просто відповідає на інше питання. IDE запитує: «Чи можна швидко попрацювати?», а jar запитує: «Чи можна жити без твоєї IDE?».

4. Типові помилки під час роботи з трьома режимами запуску

Майже всі помилки в цій темі виглядають однаково: людина запускає застосунок одним способом, а потім робить висновки про інший. Це як перевіряти, що човен не тоне, сидячи у ванні. Ніби й схоже на воду, але деталі трохи відрізняються — і саме деталі потім кусаються.

Помилка №1: «Якщо працює в IDE — значить готово».
IDE справді вміє запускати Spring Boot сервіс, і на етапі навчання це надзвичайно важливо. Але готовність до запуску як артефакт перевіряється лише тоді, коли ви запускаєте артефакт. Інакше ви несвідомо доводите лише те, що у вас правильно налаштована IDE.

Помилка №2: плутати bootRun і java -jar.
bootRun — це запуск застосунку з проєкту в розгорнутому вигляді. java -jar — запуск одного зібраного файла. У них різні джерела класів і ресурсів, а отже, різні класи проблем. Якщо сприймати їх як одне й те саме, ви будете «лікувати симптоми», а не розуміти причини.

Помилка №3: зберігати важливі параметри запуску лише в IDE Run Configuration.
Сюди потрапляють профілі, порти, змінні середовища і будь-які override-значення. Поки ви самі, здається, що це зручно. Щойно ви передаєте проєкт іншому розробникові або намагаєтеся запустити jar — це перетворюється на «а в мене працює». Правильний напрямок мислення: параметри мають передаватися через аргументи командного рядка / змінні середовища / зовнішні конфіги, а IDE нехай просто повторює цей сценарій.

Помилка №4: запускати «не той jar» і порівнювати його з «новим кодом».
Це класика жанру: ви змінили код, забули пересобрати, запустили старий jar і здивувалися, що нічого не змінилося. java -jar запускає файл, а не ваші думки про те, що ви точно все зберегли. Якщо поведінка не збігається з очікуванням, одне з перших запитань має бути максимально нудним: «А я точно пересобрав артефакт?»

Помилка №5: вважати, що відносні шляхи завжди однакові.
В IDE робочий каталог часто виставлений у корінь проєкту автоматично, і ви звикаєте, що відносні шляхи «просто працюють». Але jar можна запустити з будь-якої папки, і тоді відносні шляхи починають означати зовсім інше. Навіть якщо ви ще не зіткнулися з цим особисто, краще завчасно пам’ятати: поточний каталог процесу — частина середовища запуску, а не властивість вашого коду.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ