1. Web-вхід і роль web-starter
Слово «сервіс» звучить солідно. Здається, ніби достатньо створити проєкт catalog-service, додати кілька класів і гордо сказати: «У мене є сервіс». Але якщо до нього не можна звернутися ззовні — наприклад, через браузер або curl, — то фактично це все ще внутрішня бібліотека, яка живе лише у вашій IDE. Вона може бути дуже розумною, але ззовні її ніхто не побачить.
Для бекенд-розробника «жити» зазвичай означає дві речі: застосунок запускається і слухає вхідні запити. Поки немає web-входу, ви не можете вручну перевірити контракт, не можете дати посилання колезі й навіть не можете просто відкрити http://localhost:... і побачити реакцію застосунку. Це трохи схоже на кафе, де ідеальна кава, класна музика і привітний бариста… але вхід замуровано. Бариста щасливий, користувачі — ні.
Важливо зрозуміти одну річ: web-вхід у Boot з’являється не тому, що ви «написали controller». Controller — це лише правило: «Якщо прийшов запит за таким шляхом — викликати такий метод». Але щоб запит узагалі міг дійти до застосунку, йому потрібен web-runtime: вбудований сервер, інфраструктура MVC та базові компоненти, які вміють приймати HTTP і зіставляти його з вашим кодом.
Дуже зручно, що Spring Boot не змушує вас збирати це вручну. Він пропонує рівно один інженерний крок: підʼєднати правильний starter. І саме це — наш перший крок до вебсервісу.
2. Базовий стек курсу: spring-boot-starter-webmvc
Коли людина вперше робить вебзастосунок на Java, у неї часто вмикається режим «а що, якщо…». А що, якщо взяти інший сервер? А що, якщо одразу reactive? А що, якщо вручну під’єднати лише потрібні модулі, щоб було «мінімально»? Усе це звучить героїчно, але для Juniorʼа зазвичай закінчується однаково: половина дня йде на складання пазла, а не на розуміння, що взагалі відбувається.
Тому в курсі ми свідомо й доволі жорстко фіксуємо web-baseline: spring-boot-starter-webmvc. Це наш «офіційний вхід» у вебсвіт курсу з трьох причин.
Перша причина — передбачуваність. Servlet-стек і Spring MVC дають дуже прямолінійну модель: прийшов HTTP-запит, він потрапив у застосунок, знайшовся обробник, повернулася відповідь. Тут мінімум «прихованих шарів мислення» для новачка. Не потрібно одразу перепрошивати мозок під інші парадигми — нам достатньо стійкої моделі request/response.
Друга причина — узгодженість платформи. Starter — це заздалегідь узгоджений набір залежностей, який перебуває під керуванням Spring Boot BOM. Тобто ви не «вгадаєте» і не поставите несумісні версії бібліотек випадковим рухом руки. Це саме та ситуація, коли фреймворк чесно робить ваше життя простішим, а не «відбирає свободу».
Третя причина — дисципліна курсу. Нам важливо тримати один стек і один шлях розвитку проєкту. Якщо сьогодні один студент під’єднає WebFlux, другий — Jetty, третій — вручну прикрутить spring-webmvc без starterʼа, то завтра ми вчитимемо не Spring Boot, а колективну археологію чужих залежностей. А археологія — це цікаво, але краще, коли ви вже Middle.
Щоб не було відчуття «мені просто заборонили інше», сформулюю м’якше: ми не кажемо «MVC кращий». Ми кажемо: «MVC — найпряміший шлях до першого робочого вебсервісу в межах Boot-курсу». А коли з’явиться міцна база, інші стеки перестануть здаватися магією і стануть просто ще однією інженерною опцією.
3. Starter і classpath
Spring Boot доволі прагматичний (у хорошому сенсі): він не намагається вгадати ваші думки — він дивиться на classpath. Є потрібні класи й залежності — значить, можна вмикати відповідну інфраструктуру. Немає — не вмикаємо. Це не «чорна магія», а прагматична стратегія: менше ручного налаштування, більше повторюваності.
Коли ви додаєте spring-boot-starter-webmvc, застосунок майже «механічно» перетворюється на вебзастосунок. І тут важливо зафіксувати центральну думку сьогоднішньої лекції: точка входу не змінюється. Не з’являється якийсь особливий web-main. Ми не переходимо на інший спосіб запуску. Ми не починаємо вручну викликати сервлети.
main() залишається тим самим, приблизно таким:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // Увімкнює сканування компонентів і автоконфігурацію Spring Boot
public class CatalogServiceApplication {
public static void main(String[] args) {
// Точка входу не змінюється: вебрежим увімкнеться завдяки залежностям у classpath
SpringApplication.run(CatalogServiceApplication.class, args);
}
}
Змінюється не код старту, а те, що він бачить навколо себе. Навколо з’являються веббібліотеки, а отже Boot може зібрати web-runtime. Якщо ви пам’ятаєте попередні лекції про автоконфігурацію, то це просто продовження тієї самої ідеї: умови спрацьовують, бо на classpath є потрібні елементи.
Можна уявити це як перемикач режимів у грі. Ви запускаєте ту саму гру, але обираєте режим «кампанія» або «мультиплеєр». Кнопка «Запуск» однакова, а світ навколо — інший. Starter — це якраз такий вибір режиму, тільки через Gradle.
Щоб картинка була зовсім чіткою, ось невелика схема:
flowchart TD
%% Додавання starter-а змінює набір залежностей, і це запускає інший ланцюжок автоконфігурації
A["build.gradle.kts: додали webmvc starter"] --> B["Classpath: зʼявилися вебзалежності"]
B --> C["Запуск Boot: спрацьовують умови автоконфігурації"]
C --> D["Застосунок стартує як вебсервлет"]
D --> E["Процес слухає порт і чекає HTTP-запитів"]
Наразі нам не потрібно занурюватися в деталі того, які саме компоненти піднімаються (це окрема тема дня). Важливо запам’ятати причинно-наслідковий зв’язок: starter → classpath → автоконфігурація → інший runtime. Це і є Boot-мислення.
4. Підключаємо starter у build.gradle.kts
На практиці підключення вебрежиму — це, звісно, не філософський трактат, а дуже конкретна правка build.gradle.kts. І тут хочеться заздалегідь уберегти вас від типової спокуси початківців: «Ну раз це веб, додам-но я spring-web, spring-webmvc, Tomcat і ще п’ять бібліотек, які знайшов в інтернеті». Це шлях у світ, де кожен наступний крок ламатиме попередній, бо ви збираєте платформу вручну.
У catalog-service ми робимо один акуратний і зрозумілий крок: під’єднуємо spring-boot-starter-webmvc як звичайну залежність implementation. Приклад мінімального фрагмента може виглядати так:
dependencies {
// Спостережуваність і базові метрики (зручно, щоб перевірити, що застосунок живий)
implementation("org.springframework.boot:spring-boot-starter-actuator")
// Spring MVC + вбудований servlet-контейнер (Tomcat за замовчуванням)
implementation("org.springframework.boot:spring-boot-starter-webmvc")
// Тестова інфраструктура Spring Boot (JUnit, AssertJ тощо)
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Зверніть увагу на два нюанси, які здаються дрібницею, але реально рятують нерви.
Перший нюанс: ми не вказуємо версії у цих залежностях. Версії приходять із Boot-платформи (BOM), а отже сумісність контролюється «централізовано». Якщо ви додасте :spring-boot-starter-webmvc:4.0.3 вручну — інколи це «нічого не зламає», а інколи ви почнете жити у світі, де один модуль працює на одній версії, а інший — на іншій. На цьому етапі курсу це майже гарантований спосіб створити собі загадку.
Другий нюанс: це саме starter, а не голий spring-webmvc. Starter дає вам підібраний набір залежностей і вбудовану зручність Boot. Ми не вдаємо, що вміємо краще за команду Boot підбирати мінімальний безпечний набір.
Після зміни build.gradle.kts зазвичай потрібно, щоб Gradle й IDE підтягнули залежності. Іноді ви буквально бачите, як у проєкту з’являється «друге життя»: завантажуються артефакти, IDE індексує класи, і раптом автодоповнення починає підказувати вебсутності. Це нормальний сигнал: classpath змінився.
Якщо хочеться переконатися, що starter справді щось приніс (а не просто анотації), можна зазирнути в runtimeClasspath. Приклад команди (без фанатизму, просто подивитися):
./gradlew dependencies --configuration runtimeClasspath | grep -E "webmvc|tomcat|jackson"
# runtimeClasspath показує, що реально опиниться в застосунку під час запуску
# grep тут просто підсвічує «підозрюваних»: MVC, вбудований сервер і JSON
Ми не робимо з цього окремого розслідування. Нам важливий сам факт: starter «тягне» за собою інфраструктуру. Саме для цього він і існує.
5. Перевіряємо web-режим
Після під’єднання starterʼа у новачків часто з’являється дивна тривога: «Я нічого не написав, а воно вже web? Так не буває». Буває. Boot якраз про те, щоб базова інфраструктура підіймалася за домовленостями, а ви додавали лише прикладний код. Але, звісно, хочеться побачити докази — і це абсолютно здорове бажання.
Найпростіший спосіб перевірити — запустити застосунок і уважно подивитися на логи запуску. Команда запуску може бути тією самою, що й раніше:
./gradlew bootRun
# Запускаємо застосунок через Gradle: так простіше повторювати однаковий сценарій на різних машинах
# Ознаки вебрежиму далі будуть видно в логах (піднявся сервер, слухається порт)
У логах ви зазвичай побачите рядки, з яких зрозуміло, що піднято вбудований сервер і він слухає порт. Приблизно в такому дусі (формулювання може відрізнятися, але сенс упізнаваний):
Tomcat started on port 8080 (http) with context path '/'
Started CatalogServiceApplication in 1.234 seconds
Далі можна зробити «найчеснішу перевірку у світі backendʼа»: постукатися в localhost. Поки у нас ще немає controllerʼів і маршрутів, корінь / відповідатиме помилкою 404 — і це нормально. Важливо: 404 означає «маршрут не знайдено», а не «застосунок не працює».
Наприклад:
curl -i http://localhost:8080/
# -i: друкуємо статус і заголовки, щоб побачити, що сервер справді відповідає
HTTP/1.1 404
Content-Type: application/json
...
Якщо ви отримали HTTP-відповідь, значить застосунок реально слухає порт, вбудований сервер піднято, і ми дійсно в web-режимі. Навіть якщо відповідь поки що некрасива, ми вже перейшли ключову межу: сервіс доступний ззовні.
І тут хороший психологічний орієнтир: ми не змінювали main(), ми не писали спеціальних конфігурацій для web, ми не створювали сервер вручну. Ми просто змінили dependency model проєкту — і Boot зібрав інший runtime. Це не «магія», а обіцянка платформи, яку вона чесно виконує.
6. Типові помилки під час web-входу
Перед тим як рухатися далі, корисно проговорити кілька помилок, які майже гарантовано трапляються у перший день «виходу в web». Вони не роблять вас поганим розробником — вони роблять вас розробником, який зараз отримає досвід і завтра перестане наступати на ті самі граблі.
Помилка № 1: підключати вебстек вручну розсипом залежностей.
Зазвичай це починається з думки «starter — це занадто багато, візьму лише spring-webmvc». А через двадцять хвилин додається ще щось «для JSON», потім щось «для сервера», потім — «чомусь не стартує» — і в підсумку отримуємо той самий starter, але зібраний випадково і з ризиком конфліктів. На рівні курсу правильніше триматися одного акуратного входу: spring-boot-starter-webmvc.
Помилка № 2: думати, що потрібен «особливий web main» або інший спосіб запуску.
Іноді хочеться переписати main() або додати якісь додаткові виклики, щоб «увімкнути web». Але web-режим у Boot вмикається не кодом запуску, а поєднанням classpath + auto-configuration. Ваш SpringApplication.run(...) залишається тим самим; змінюється те, які підсистеми Boot може зібрати навколо нього.
Помилка № 3: випадково під’єднати не той web starter (або під’єднати одразу два).
Найчастіший варіант — переплутати стек і додати webflux замість MVC, або в пориві ентузіазму додати обидва. У найкращому разі це створить плутанину, у найгіршому — почнуться дивні конфлікти і «чому воно працює не так». У межах курсу ми фіксуємо рівно один варіант: spring-boot-starter-webmvc.
Помилка № 4: побачити 404 і вирішити, що «нічого не вийшло».
Після додавання starterʼа застосунок підніме сервер, але маршрутів у нього поки немає. Тому 404 на / — очікувана поведінка. Реальна ознака успіху зараз не «красива відповідь», а те, що порт слухається і ви отримуєте будь-яку HTTP-відповідь від процесу.
Помилка № 5: забути про вже запущений процес і отримати конфлікт порту.
Якщо застосунок уже було запущено, а ви стартуєте ще одну копію (або не зупинили попередню), то порт 8080 може бути зайнятий. Тоді здається, що «starter усе зламав», хоча насправді ви просто влаштували локальну конкуренцію за один і той самий порт. Це з тих багів, які лікуються не читанням документації, а звичкою перевіряти, що саме у вас зараз запущено.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ