JavaRush /Курси /Spring Boot /Вбудований Tomcat у...

Вбудований Tomcat у Spring Boot

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

1. Роль сервера у Spring Boot

WebApplicationType уже звузив нам картину: catalog-service живе у гілці SERVLET. Отже, питання стає зовсім конкретним: хто в цьому режимі насправді відкриває порт і приймає HTTP-запити.

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

Коли ви запускаєте catalog-service у режимі SERVLET, застосунок бере на себе обов’язок приймати HTTP-запити. А щоб їх приймати, потрібно відкрити порт і обробити мережеві з’єднання. Саме тут і з’являється вебсервер — точніше, servlet-контейнер. Boot вирішує це «по-дорослому»: не змушує вас вручну налаштовувати зовнішній сервер, а приносить вбудований сервер як бібліотеку та запускає його разом із вашим застосунком.

Дві моделі Java Web: зовнішній і embedded

У Java-світі історично склалася досить зрозуміла — але не надто дружня до новачка — модель: ви пишете вебзастосунок, пакуєте його в артефакт, часто WAR, а потім розгортаєте в уже встановленому сервері застосунків або контейнері. У вас є окремо «сервер» (Tomcat/WebSphere/будь-що) і окремо ваш код, який сервер десь усередині себе запускає. Це схоже на ситуацію: ви пишете виставу, а показують її в чужому театрі за чужими правилами.

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

Щоб не залишатися лише на метафорах, зафіксуємо різницю в короткій табличці:

Питання Зовнішній контейнер (класика) Embedded server (Boot-підхід)
Хто запускає сервер? Ви окремо / через скрипт Ваш застосунок на старті
Де живе сервер? Окрема інсталяція/процес У тому ж JVM-процесі
Що ви запускаєте локально? Сервер + розгортання артефакту main() / java -jar (концептуально)
Звідки береться Tomcat? Встановлений окремо Приходить як залежність (через starter)
Що простіше пояснити новачку? «Спочатку встановіть сервер, потім розгорніть застосунок» «Запустіть застосунок — він слухає порт»

Сенс не в тому, що «класика погана». Важливо інше: Spring Boot виходить саме з embedded-моделі, коли застосунок сам підіймає свій сервер. Для більшості невеликих і середніх сервісів це й є звична робоча норма.

2. Embedded Tomcat в одному процесі

Слова «embedded server» звучать так, ніби у вашому проєкті сховався мінісервер у коробочці, і ви його «вбудовуєте». Насправді все прозаїчніше й, що приємно, зрозуміліше. Embedded server — це набір бібліотек, jar-файлів, які потрапляють у ваш classpath і вміють підіймати HTTP-слухач. А Spring Boot уміє знайти ці бібліотеки, створити потрібні об’єкти та запустити їх у правильний момент.

Ключовий злам мислення тут такий: в embedded-підході сервер — це не місце, куди ви «заливаєте» застосунок. Сервер — це частина застосунку. У вас не два самостійні світи, які треба вручну подружити, а один світ: JVM-процес, усередині якого Boot збирає інфраструктуру та ваш код.

Можна уявити запуск — дуже спрощено — як такий потік:

flowchart TD
    A["main()"] --> B["SpringApplication.run(...)"]
    B --> C["Boot визначає WebApplicationType = SERVLET"]
    C --> D["Створюється servlet-web ApplicationContext"]
    D --> E["refresh() контексту + auto-configuration"]
    E --> F["Створюється вбудований Tomcat"]
    F --> G["Tomcat відкриває порт і починає слухати"]
    G --> H["Застосунок готовий приймати HTTP-запити"]

Порядок тут принциповий. Спочатку Boot розуміє, що перед ним SERVLET-застосунок, потім обирає servlet-web контекст, і лише вже всередині його refresh() та auto-configuration підіймає сервер. Тобто Tomcat — не причина вебрежиму, а наслідок уже вибраної гілки старту.

Це не «внутрішня кухня фреймворку», яку треба вчити напам’ять. Це карта місцевості, щоб ви розуміли, чому після запуску Boot-застосунку у вас раптово з’являється порт, а в логах — рядки про Tomcat.

3. Звідки береться Tomcat: starter і залежності

Найпростіша й найчесніша відповідь на питання «звідки взявся Tomcat?» звучить трохи нудно, зате чудово рятує від міфології: Tomcat узявся із залежностей. Не з повітря, не з «Spring-магії», а з build.gradle.kts. І тут нам дуже доречно все, що ми вже обговорювали раніше: стартери й транзитивні залежності.

Якщо в проєкті є spring-boot-starter-webmvc, то ви не просто «підʼєднали MVC». Ви підʼєднали узгоджений набір бібліотек для servlet-вебзастосунку, серед яких за замовчуванням є embedded Tomcat.

Мініфрагмент build.gradle.kts — лише ідея, не повний файл — виглядає так:

dependencies {
    // Підʼєднуємо servlet-web стек: разом із ним приїде вбудований Tomcat (транзитивно)
    implementation("org.springframework.boot:spring-boot-starter-webmvc")

    // Тестовий starter: базові залежності для тестів (JUnit, Assert-бібліотеки тощо)
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

З погляду новачка важливо помітити одну річ: ви не додавали tomcat-embed-core вручну. І це нормально. Starter підтягнув залежності транзитивно, а Boot-BOM зафіксував сумісні версії. Тому ви отримуєте «робочу збірку сервера» без ручних танців із версіями.

Якби ви підʼєднали лише базовий starter без вебчастини, картина була б іншою:

dependencies {
    // Базовий Spring Boot без web-стека: контекст підніметься, але сервер не слухатиме порт
    implementation("org.springframework.boot:spring-boot-starter")
}

У такому проєкті Boot залишається Boot, контекст залишається контекстом, IoC/DI залишаються IoC/DI — але порт слухати нікому, бо web-стека немає.

Щоб відчути «склад» web-starter, корисно знати назви деяких бібліотек, які зазвичай видно в dependency tree, — у спрощеному вигляді:

spring-boot-starter-webmvc
\--- spring-boot-starter-tomcat
     \--- tomcat-embed-core
     \--- tomcat-embed-websocket

Не потрібно зараз запам’ятовувати ці артефакти. Важливо інше: Tomcat з’являється в проєкті саме тому, що ви підʼєднали вебстартер, а не тому, що Boot «любить Tomcat і ставить його всім примусово».

4. Запуск і керування Tomcat у Boot

Хороше питання новачка: «Окей, Tomcat прийшов залежністю. Але де код, який його запускає? Чому я не бачу new Tomcat()?» І тут знову допомагає вже зібрана ланка старту: SpringApplication спочатку обирає WebApplicationType.SERVLET, за цим вибором створює веборієнтований контекст, а потім під час його refresh() та auto-configuration підіймає сервер.

І тут ми знову впираємося у фундаментальну ідею Spring: інфраструктурні об’єкти створює контейнер, а не ви вручну.

У servlet-режимі Boot підіймає спеціальний тип контексту, який уміє працювати з вебсервером. На цьому рівні вам важливо розуміти не назви класів, а відповідальність: Boot створює «фабрику сервера», створює сервер, запускає його, а потім пов’язує з вебшаром Spring — наприклад, із DispatcherServlet. Це відбувається всередині тієї самої великої «ланки запуску», яку ми обговорювали в лекції про SpringApplication.run(...).

Тобто ваша програма запускається не за схемою «я створив сервер, потім додав у нього Spring». Вона запускається за схемою «Spring Boot зібрав контекст, побачив вебстек, підняв сервер як інфраструктуру та зробив застосунок готовим до запитів».

І це, до речі, дуже зручна дисципліна відповідальності: ваш код залишається прикладним — каталог курсів, — а сервер залишається інфраструктурою — як пам’ять, мережа чи файли. Коли ви починаєте писати new Tomcat() у прикладному коді, ви ніби кажете: «Я — головний адміністратор цього світу». Зазвичай це закінчується тим, що світ відповідає взаємністю і починає ламатися в неочікуваних місцях.

5. Міні-доказ: сервер усередині застосунку

Зараз буде маленький трюк «заради розуміння», не заради «так треба писати завжди». Ми знаємо, що SpringApplication.run(...) повертає ApplicationContext. У servlet-режимі цей контекст — веборієнтований, і в ньому є інформація про піднятий сервер.

Можна — лише для демонстрації — у тому ж CatalogServiceApplication, одразу після звичайного run(...), вивести порт, на якому реально стартував сервер:

var ctx = SpringApplication.run(CatalogServiceApplication.class, args);

// У servlet-режимі контекст — це ServletWebServerApplicationContext,
// у якому можна отримати доступ до піднятого web-сервера
var webCtx = (ServletWebServerApplicationContext) ctx;

// Дістаємо реальний порт, який слухає embedded server (зазвичай 8080 за замовчуванням)
System.out.println("Порт вбудованого сервера = " + webCtx.getWebServer().getPort());
// Порт вбудованого сервера = 8080

Зверніть увагу на сенс: ми не «ходимо в Tomcat як в окремий світ». Ми беремо контекст, який Boot зібрав, і з нього дізнаємося параметри сервера. Це хороший ментальний якір: embedded server — не зовнішня інсталяція, а частина рантайму, яким керує Boot.

І так, у реальному коді ви зазвичай не робитимете такі println у main(). Але як спосіб зрозуміти модель — працює чудово.

6. Ознаки та нюанси запуску сервера

Як побачити старт Tomcat у логах

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

Приклад типових рядків — спрощено, формат може трохи відрізнятися:

Tomcat initialized with port 8080 (http)
Starting service [Tomcat]
Starting Servlet engine: [Apache Tomcat/10.x]
Tomcat started on port 8080 (http) with context path ''

У цій лекції нам важливі дві ідеї. Перша: порт «не з’явився сам», це результат того, що embedded Tomcat справді запущений. Друга: ці рядки з’являються лише тоді, коли застосунок стартує в servlet-режимі і web-стек справді присутній у залежностях.

Корисно вже зараз зафіксувати, що означає рядок про Tomcat: це один із найпростіших ознак того, що ви справді запустили вебрантайм, а не просто Java-застосунок, який щось робить усередині себе.

Вебстартер і запуск без контролерів

Є одна пастка, в яку потрапляють новачки: вони думають, що сервер «з’являється», коли написали перший @RestController. Насправді сервер з’являється раніше — у момент, коли Boot визначив WebApplicationType.SERVLET і підняв embedded-контейнер.

Тобто навіть якщо у вас поки немає жодного контролера, сервер може стартувати, порт відкриється, і ви зможете постукати в застосунок браузером. Найімовірніше, ви отримаєте 404 — що логічно: «маршрутів немає», — але сам факт життя сервера від цього не зникає. Це важливо психологічно: інфраструктура вебзастосунку — не «наслідок наявності коду контролера», а «наслідок обраного рантайм-режиму та classpath».

І у зворотний бік це теж працює. Якщо вимкнути web-starter або явно виставити WebApplicationType.NONE, як ми обговорювали в попередній лекції, ніякого Tomcat не буде, навіть якщо ви випадково залишите десь вебкласи. У Boot спочатку визначаються режим та інфраструктура, а вже потім усе інше.

Tomcat за замовчуванням і вибір контейнера

У новачків інколи виникає підозра: «Якщо в проєкті Tomcat, значить Spring Boot = Tomcat?» Ні. Boot не дорівнює Tomcat — так само як Java не дорівнює IntelliJ IDEA, хоча інколи здається, що без неї Java не запускається.

Tomcat — це типовий embedded servlet-контейнер для вебстартерів у більшості стандартних конфігурацій. Типовий вибір зроблено не тому, що «решта погані», а тому, що Tomcat широко поширений, добре задокументований, і вбудована модель у нього давно відпрацьована.

За бажанням Tomcat можна замінити на Jetty або Undertow — корисно знати, що такі двері існують. Але спочатку краще зафіксувати саму модель: сервер приходить як залежність і керується Boot, а не обирається вручну як «наймодніший контейнер тижня».

Якщо вам дуже хочеться мислити на майбутнє, тримайте в голові просту думку: embedded-модель зберігається, а конкретна реалізація контейнера може змінюватися. Але сьогодні нам потрібна базова зв’язка: web-starter приносить servlet-стек, а той — embedded Tomcat.

7. Типові помилки під час роботи з embedded server

Помилка № 1: чекати «коду запуску Tomcat» у main() і намагатися написати його вручну.
Це нормальна реакція людини, яка звикла до new і до явного керування. Але в Spring Boot саме інфраструктура — сервер, конвертери, частина вебшару — підіймається контейнером. Якщо почати вручну створювати і запускати сервер, ви ламаєте ідею auto-configuration і отримуєте два конкуруючі світи: «мій Tomcat» і «Tomcat, який очікує Boot».

Помилка № 2: думати, що сервер з’являється лише після появи класів-контролерів.
Контролери — це прикладний шар. Сервер — інфраструктурний. Boot підіймає сервер, бо побачив servlet web-стек на classpath і обрав WebApplicationType.SERVLET. Тому сервер може стартувати і в «порожньому» застосунку без ендпоінтів, і це не баг, а правильно зібрана інфраструктура.

Помилка № 3: не пов’язувати факт підняття сервера із залежностями.
Коли людина бачить порт 8080 і рядки про Tomcat, їй здається, що це «сам Boot так вирішив». Насправді це наслідок spring-boot-starter-webmvc і транзитивних залежностей. Якщо прибрати web-starter, сервер зникне. Якщо додати — з’явиться. Саме так і треба мислити Boot-платформою: classpath і стартери впливають на рантайм.

Помилка № 4: плутати embedded server і «якийсь окремий процес на комп’ютері».
Інколи новачок запускає застосунок, бачить «Tomcat started on port 8080» і починає шукати, «де в диспетчері завдань окремий Tomcat». Його немає. Це той самий Java-процес. Тому зупинка застосунку в IDE зупиняє і сервер, і все інше. І навпаки: якщо порт зайнятий, це проблема вашого процесу та його середовища, а не «десь у системі стоїть неправильний Tomcat».

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

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