1. Карта маршрутів під час виконання й IDE
Коли ви тільки починаєте писати бекенд, світ здається простим: ось клас-контролер, ось @GetMapping — отже, endpoint існує. Але реальність любить підкидати сюрпризи: контролер може не потрапити до компонентного сканування, маршрут може виявитися іншим через спільний префікс, метод може очікувати інший HTTP-метод, а частину шляхів узагалі обслуговують не контролери, а статичні ресурси чи інфраструктура. І в цей момент ви починаєте… вгадувати.
Endpoint /actuator/mappings — це як «карта метро» вашого сервісу. Не з умовної схеми, а з реального стану сервісу: які станції (маршрути) існують, куди ведуть і які лінії (MVC/Actuator/static resources) там перетинаються. Дуже зручно, коли ви ловите 404/405 і хочете спочатку переконатися, що маршрут узагалі зареєстрований, а вже потім шукати проблему в контролері.
Що вважається mapping у Spring MVC
Якщо говорити по-людськи, “mapping” у Spring MVC — це правило «який код має обробити який HTTP-запит». І це правило зазвичай ширше, ніж просто рядок /api/catalog/courses. До mapping майже завжди входить HTTP-метод (GET, POST тощо), іноді — query-параметри, заголовки, Content-Type (що ми приймаємо) і Accept (що ми віддаємо). Новачки часто перевіряють лише шлях, а потім дивуються 405 або 415.
Давайте на короткому прикладі подивимося, як mapping працює в комплексі. Це один і той самий шлях, але різні правила, а отже потенційно — різні обробники:
package com.example.catalogservice.catalog.web;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // <-- Важливо: без цієї анотації Spring не зареєструє контролер, і mapping не зʼявиться під час виконання.
class DemoController {
@GetMapping(
path = "/api/demo",
produces = MediaType.APPLICATION_JSON_VALUE // <-- Це теж частина mapping: ми явно кажемо, що віддаємо JSON.
)
String json() {
// Так читається краще, ніж екранований рядок: одразу видно, що саме ми повернемо клієнту.
return """
{"ok":true}
""";
}
}
Навіть тут уже видно: mapping — це не просто "/api/demo", а «GET + шлях + produces JSON». А в реальному catalog-service ми ще додаємо @RequestMapping("/api/catalog"), path variables (/{slug}) і фільтри через query params. Тому іноді корисно мати інструмент, який скаже: «ось це правило реально існує під час виконання, і ось цей метод-обробник його обслуговує».
2. Як працює /actuator/mappings
Найважливіше: mappings не «сканує код» і не будує здогадки. Він показує те, що Spring уже зареєстрував під час запуску застосунку. Тобто це знімок реального стану вебшару на поточний момент: які обробники знайшлися, які маршрути склалися, які інфраструктурні речі теж беруть участь в обробці запитів.
Якщо уявити Spring MVC як диспетчера таксі, то DispatcherServlet — це диспетчер, який приймає заявку (HTTP-запит) і вирішує, яке «таксі» (метод-обробник) поїде. Endpoint mappings показує, які «таксі» взагалі зареєстровані і за якими адресами вони їздять.
Ось спрощена схема, де mappings допомагає підсвітити карту:
flowchart TD
A["HTTP-запит"] --> B["DispatcherServlet"]
B --> C["HandlerMappings: пошук відповідного обробника"]
C --> D["Контролер або обробник ресурсу"]
D --> E["Відповідь"]
M["/actuator/mappings"] --> C
M -->|"показує список зареєстрованих правил"| C
Практично це означає дві корисні речі. По-перше, /actuator/mappings показує не лише ваші маршрути @RestController, а й інфраструктурні: статичні ресурси, Actuator-маршрути тощо. По-друге, він допомагає перестати сперечатися з реальністю. Якщо endpoint не відображається в mappings, то він або не зареєструвався, або ви дивитеся не той контекст, порт чи профіль, або просто очікували не того.
3. Як увімкнути mappings у catalog-service
В Actuator дуже легко влаштувати собі пастку: «для зручності» відкрити *, а потім забути, що зручність була лише локально. Ми вже домовилися про здорову політику: у local/dev можна розширену діагностику, у prod — мінімум. Тому mappings зазвичай вмикають лише в local/dev і лише тоді, коли він справді потрібен для діагностики.
Якщо експонування для local/dev у вас уже налаштовано, просто додайте туди mappings. Нижче — фрагмент для поточного стану catalog-service: зберігаємо вже знайомі config-introspection і conditions, а не збираємо include заново.
management:
endpoints:
web:
exposure:
# Зберігаємо вже відкриті local/dev endpoints і додаємо mappings
include: "health,info,env,configprops,conditions,mappings"
Зверніть увагу на характерну деталь: тут немає *. Вам майже ніколи не потрібно «все». Потрібно кілька конкретних інструментів, і mappings — один із них.
Якщо у вашому проєкті вже є розширена політика для local/dev (наприклад, ви відкриваєте ще conditions або configprops), це теж нормально. Важливо, щоб це було свідомо, і щоб у prod ви не винесли «діагностичний швейцарський ніж» назовні.
4. Як читати /actuator/mappings
Перший досвід із /actuator/mappings у новачка часто такий: «Ого, скільки всього… я відкрив — і спершу навіть злякався». Це нормальна реакція. Там справді багато інформації, тому що Spring показує усю вебкартину, включно з інфраструктурою. Але читати це потрібно не як книгу, а як довідку для конкретного запиту.
Корисна звичка — подумки тримати три кроки: ви шукаєте шлях, перевіряєте HTTP-метод, потім дивитеся, який обробник (клас і метод) призначено на це правило. Усе інше — другорядне.
Приклад того, як виглядає шматок відповіді (дуже спрощено і скорочено, бо повний JSON може бути величезним):
{
"contexts": {
"application": {
"mappings": {
"dispatcherServlets": {
"dispatcherServlet": [
{
"handler": "CourseCatalogController#courses(...)",
"predicate": "{GET [/api/catalog/courses]}"
}
]
}
}
}
}
}
Назви полів можуть бути трохи різними залежно від версії Boot, але ідея одна: ви побачите, що є розділ про MVC-маршрути (зазвичай прив’язано до dispatcherServlet), і всередині будуть правила з предикатами на кшталт «GET + шлях», а також посилання на метод-обробник.
Щоб трохи структурувати «що де лежить», корисно тримати в голові таку таблицю — як орієнтир, а не як те, що потрібно заучувати:
| Шматок у mappings | Що це означає по суті | Що ви там зазвичай шукаєте |
|---|---|---|
| dispatcherServlets | Маршрути Spring MVC (ваші контролери й обробники статичних ресурсів) | /api/catalog/**, / (якщо він через MVC), обробку статичних ресурсів |
| servlets | Зареєстровані servlet’и | наявність dispatcherServlet, інколи management servlet |
| servletFilters | Фільтри, через які проходить запит | корисно, якщо ви розбираєте, чому запит змінився, але це вже більш просунутий рівень |
| Блоки про Actuator endpoints | Маршрути management endpoint'ів | /actuator/health, /actuator/info, /actuator/mappings і хто їх обслуговує |
На практиці в 90 % випадків вам достатньо першого рядка: dispatcherServlets. Тому що ви шукаєте ваші API-endpoint'и, а вони живуть саме там.
5. Що видно в catalog-service
Наш навчальний сервіс до цього моменту вже працює: він віддає HTML-лендінг (/), віддає read-only JSON-дані по каталогу і має Actuator. Тому в mappings ви зазвичай хочете підтвердити, що піднялися щонайменше такі прикладні маршрути:
/api/catalog/courses
/api/catalog/courses/{slug}
/api/catalog/featured
/api/catalog/tracks
Покажу приклад коректного контролера в стилі проєкту, щоб було зрозуміло, які речі мають збігатися в runtime-карті:
package com.example.catalogservice.catalog.web;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // <-- Робимо bean, щоб Spring зареєстрував handler methods.
@RequestMapping("/api/catalog") // <-- Спільний префікс теж бере участь у підсумковому mapping.
class CourseCatalogController {
@GetMapping("/tracks") // <-- Підсумковий шлях буде: /api/catalog/tracks
List<String> tracks() {
// Для навчального проєкту повертаємо "заглушку", щоб було що побачити у відповіді.
return List.of("SPRING", "DATA", "INFRA");
}
}
Якщо такий контролер справді зареєстровано, то в /actuator/mappings ви побачите mapping з предикатом на кшталт GET [/api/catalog/tracks] і method handler, схожий на CourseCatalogController#tracks().
І це дуже практично: коли ви відкриваєте mappings, ви підтверджуєте не «я ніби написав метод», а «контейнер справді знає, що цей endpoint існує».
6. Сценарії, де mappings економить години
“404: endpoint не знайдено” — контролер не зареєструвався
Дуже класична історія: ви написали новий controller або новий метод, запустили застосунок, і… 404. Починається ритуальний танець: «може, порт не той, може, кеш, може, Postman дивиться не так». Перше, що варто зробити, — відкрити /actuator/mappings і знайти шлях. Якщо шляху там немає, то проблема не в «мережевих феях», а в реєстрації.
Одна з найбанальніших причин — забули анотацію @RestController (або хоча б @Controller). У підсумку Spring сприймає клас просто як звичайний Java-клас, і mapping'и не з’являються.
Порівняйте:
package com.example.catalogservice.catalog.web;
import org.springframework.web.bind.annotation.GetMapping;
class BrokenController {
// Важливо: тут НЕМАЄ @RestController/@Controller, тому Spring не вважатиме цей клас контролером.
@GetMapping("/api/broken")
String broken() {
return "oops";
}
}
Цей код компілюється, IDE радіє, але Spring не побачить його як контролер. У mappings його не буде, і це чесний сигнал: «маршрут не зареєстровано». Виправлення тут банальне (і нехай воно буде банальним — ми вчимося):
package com.example.catalogservice.catalog.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // <-- Тепер Spring зареєструє handler method, і він зʼявиться в /actuator/mappings.
class FixedController {
@GetMapping("/api/fixed")
String fixed() {
return "ok";
}
}
Після цього маршрут з’являється і в mappings, і в реальному виклику.
“405 Method Not Allowed” — HTTP-метод інший
Друга типова проблема: ви натискаєте “Send” у Postman, бачите 405 і думаєте: «але ж я точно зробив endpoint!». Так, зробили. Але не той, який ви викликаєте.
Наприклад, у catalog-service майже все GET. Якщо ви випадково надішлете POST на /api/catalog/courses, то endpoint може існувати, але бути прив’язаним до іншого методу. І ось тут mappings допомагає не домислювати, а перевірити.
У контролера, наприклад, так:
package com.example.catalogservice.catalog.web;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
class CourseReadController {
@GetMapping("/api/catalog/courses") // <-- Mapping лише для GET (POST сюди не підходить).
List<String> courses() {
// Сенс прикладу: показати, що endpoint є, але він "прив'язаний" до HTTP-методу.
return List.of("spring-boot", "spring-core");
}
}
У mappings ви побачите саме GET [/api/catalog/courses]. А якщо ви надсилаєте POST, то координати не збігаються: шлях той самий, метод — ні. На рівні HTTP саме так і виникає 405.
“/ працює без контролера” — static resources теж у карті
Це улюблена пастка після теми статичних ресурсів. Ми додавали static/index.html, і тепер GET / віддає сторінку. Новачок іноді щиро вважає, що «все, що віддається, обов’язково має мати контролер». Але у Spring Boot є вбудований обробник статичних ресурсів, і welcome page — його частина.
Коли ви дивитеся mappings, ви часто побачите, що / або /** обслуговуються не вашим контролером, а ResourceHttpRequestHandler (або подібним обробником ресурсу). Це нормально і навіть корисно: mappings дозволяє відрізнити «це контролер» від «це ресурси». А ще це допомагає пояснити дивні ситуації на кшталт «я зробив контролер на /, але чомусь відповідає не він». Іноді статичний ресурс і ваш mapping починають конфліктувати, і ви бачите це прямо по карті.
Для навчального catalog-service це особливо приємно: landing page у нас мінімальна, але сервіс сприймається як справжній, і mappings показує, за рахунок якої інфраструктури це працює.
“Actuator endpoint недоступний” — перевірка exposure-політики
Якщо ви дотримуєтеся profile-стратегії (а ми дотримуємося), то набір доступних Actuator endpoint'ів відрізняється в local/dev і prod. Тому ситуація «вчора /actuator/mappings відкривався, а сьогодні ні» зазвичай означає не містику, а те, що ви запустилися з іншим профілем або змінили політику експонування.
mappings хороший тим, що він показує не лише ваші API, а й management-маршрути. Тобто ви можете перевірити: endpoint справді зареєстровано й експоновано, чи він узагалі не піднімався.
При цьому пам’ятайте важливе логічне коло: щоб подивитися mappings, його самого треба спершу експонувати. Тому якщо ви сховали його в prod, то в prod ви його й не побачите — і в межах нашої політики це, строго кажучи, правильно.
Маленький лайфхак: як шукати потрібне в mappings
Коли JSON великий, очі швидко втомлюються й уже не хочуть читати його вручну. Тому краще застосовувати «ледачі» техніки: відкрити в браузері та скористатися пошуком по сторінці (Ctrl+F) за вашим шляхом, або зробити запит через curl і пошукати підрядок. Це не «хакерство», а нормальна інженерна гігієна: у комп’ютера краще виходить шукати рядки, ніж у людини після третьої чашки кави.
Наприклад, для catalog-service ви зазвичай шукаєте /api/catalog або конкретний endpoint /api/catalog/courses. Важливий момент: шукайте не лише шлях, а й поруч перевіряйте HTTP-метод, тому що один і той самий шлях у теорії може мати кілька handler'ів за різними методами. У навчальному проєкті ми таким не зловживаємо, але світ загалом підступний.
7. Типові помилки під час роботи з mappings
Помилка № 1: сприймати mappings як «документацію API».
Цей endpoint показує, які маршрути зареєстровано, але не пояснює контракт, формат JSON, зміст параметрів і бізнес-правила. Якщо ви починаєте «документувати API» через mappings, ви отримаєте список адрес без змісту. Це корисно для діагностики, але не для клієнтів вашого API.
Помилка № 2: дивитися лише на URL і ігнорувати HTTP-метод.
405 Method Not Allowed зазвичай виглядає як загадка лише в перші два рази. Потім ви розумієте, що маршрут — це «метод + шлях», і починаєте перевіряти обидва. mappings якраз виводить предикати, де метод видно явно, тому ігнорувати його — це все одно що шукати квартиру, знаючи лише вулицю, але не номер будинку.
Помилка № 3: не відокремлювати прикладні маршрути від інфраструктурних.
У mappings буде багато того, що ви не писали: ресурсні обробники, Actuator endpoints, іноді якісь внутрішні речі MVC. Якщо ви починаєте аналіз зі слів «чому тут стільки дивних маршрутів», ви швидко поїдете вбік. Нормальна стратегія — спочатку знайти свої /api/catalog/**, і лише потім, якщо потрібно, дивитися інше.
Помилка № 4: відкривати mappings «про всяк випадок» усюди, включно з prod.
З точки зору безпеки й «інформаційної щедрості» це сумнівна ідея. mappings розкриває структуру застосунку: які endpoint'и існують, які класи їх обслуговують, яка інфраструктура підключена. У local/dev це корисно, а в production-середовищі це частіше зайва підказка для допитливих. У нашому курсі здорова позиція проста: розширена діагностика — локально, мінімум — у проді.
Помилка № 5: намагатися за mappings зрозуміти, «що не так у бізнес-логіці».
Якщо у вас повертається не той список курсів або фільтрація працює не так, mappings не допоможе. Він відповідає на питання «куди потрапив запит», а не «що всередині методу відбувається». Це як перевіряти карту метро, щоб зрозуміти, чому вам не сподобалася шаурма біля станції: карта корисна, але не з цієї причини.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ