JavaRush /Курси /Spring Boot /Endpoint conditions...

Endpoint conditions і автоконфігурація

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

1. Boot «зробив щось сам»

Базовий Actuator уже дав нам health, info і звичку акуратно поводитися з політикою exposure. Тепер нам потрібен наступний щабель діагностики: зрозуміти, чому Boot увімкнув саме такі гілки конфігурації, що реально піднялося у web-шарі, як застосунок дійшов до READY, що відбувається під час запитів і як окремо перевірити, що сервіс не просто живий, а ще й корисний. Почнемо з найнеприємнішого запитання: чому застосунок узагалі зібрано саме так.

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

Доросле інженерне запитання звучить так: «Чому застосунок зібрано саме так?» Не «що він робить», не «як виправити симптом», а саме «чому так сталося». І це запитання буває дуже практичним. Наприклад, чому в нас піднявся вбудований сервер, чому JSON віддається без наших налаштувань, чому якийсь бін зʼявився «сам», а інший не зʼявився, хоча ми впевнені, що має.

Endpoint conditions — це спосіб отримати відповідь у стилі «тому що…» з переліком причин. Він перетворює «магічну поведінку» на доведену поведінку. І це, чесно кажучи, краще за будь-який шаманський танець навколо application.yaml.

2. Що показує endpoint conditions

Ззовні все виглядає просто: ви заходите на /actuator/conditions і отримуєте величезний JSON. Усередині сенс дуже конкретний: це представлення під час виконання того, що Spring Boot вирішив увімкнути або вимкнути на етапі auto-configuration, і чому він так вирішив. Тобто ми не вгадуємо «мабуть, через профіль», а читаємо: «увімкнено, тому що такий-то клас знайдено на classpath», або «не увімкнено, тому що property має значення X», або «не увімкнено, тому що вже існує користувацький бін».

У conditions є важлива особливість, яку потрібно прийняти заздалегідь: negative matches — це не помилка. У типовому застосунку негативних збігів буде багато, тому що Boot містить гілки конфігурації на всі випадки життя: різні web-рушії, різні формати, різні середовища. Ваш catalog-service — невеликий сервіс. Йому не потрібно вмикати «все». Тому величезний список «не збіглося» — це нормальна картина, а не привід для паніки.

Щоб було легше тримати в голові, можна мислити так: conditions — це «протокол рішення» щодо інфраструктури. Він каже не тільки «винен / невинний» (увімкнено / вимкнено), а й наводить аргументацію. Якщо ви колись читали судові рішення або хоча б користувацьку угоду, то ідея схожа: тексту багато, але читати треба цілеспрямовано.

Невелика таблиця, яка допомагає зорієнтуватися в тому, що ви побачите:

Частина відповіді conditions Як це зрозуміти по-людськи Коли дивитися
positiveMatches «Ця конфігурація увімкнулася, і ось причини» Коли ви питаєте «чому воно взагалі зʼявилося?»
negativeMatches «Ця конфігурація не увімкнулася, і ось причини» Коли ви чекаєте функціональність, але її немає
unconditionalClasses (або аналогічний блок) «Це вмикається завжди, без умов» Коли здається, що «це точно auto-config?», а воно базове

Назви полів можуть трохи відрізнятися залежно від версії, але сенс у всіх реалізацій один: показати набір джерел, зазвичай auto-configuration класи, і результати перевірки умов.

3. Як увімкнути conditions у catalog-service

Endpoint conditions — дуже корисний, але він належить до діагностичних. Він показує внутрішню картину збирання застосунку, а це не те, чим потрібно тішити випадкову людину зовні. Тому ми робимо рівно те, що вже закріпили для Actuator: вмикаємо й експонуємо такі речі лише в локальному профілі або в dev, якщо у вас є окреме dev-середовище.

Найпростіший і найзрозуміліший варіант — додати conditions до вже наявного списку експонованих endpoints у application-local.yaml. Якщо у вас там уже є env або configprops, не замінюйте їх цією строкою: сенс у тому, щоб додати conditions, а не переписати весь include з нуля.

# src/main/resources/application-local.yaml
management:
  endpoints:
    web:
      exposure:
        # Зберігаємо вже відкриті local/dev endpoints і додаємо conditions
        include: "health,info,env,configprops,conditions"

Тепер, якщо ви запускаєте застосунок із профілем local, у вас зʼявиться доступ до:

GET http://localhost:8080/actuator/health

GET http://localhost:8080/actuator/info

GET http://localhost:8080/actuator/conditions

Якщо у вас у local уже залишені env і configprops, вони продовжать працювати в цьому ж профілі.

Якщо профілі у вас уже налаштовані і ви звикли запускати застосунок так:

# Запуск застосунку з активним профілем local
# (важливо: саме профіль визначає, які endpoints будуть експоновані)
./gradlew bootRun --args='--spring.profiles.active=local'

то все має бути передбачувано: endpoint зʼявиться в local і зникне в prod, якщо там ви його не експонували.

Іноді новачок потрапляє в пастку: «я додав до include, але endpoint усе одно недоступний». У такому разі важливо памʼятати дві речі. По-перше, endpoint може бути не на classpath, якщо ви не підключили spring-boot-starter-actuator (у нас він підключений). По-друге, endpoint може бути увімкнений, але не експонований, або навпаки — залежно від політики. На нашому курсі ми тримаємо модель простою: у local експонуємо явно.

Для перевірки можна зробити просту річ: відкрити GET /actuator — там зазвичай видно, які endpoints узагалі доступні в поточному профілі. І вже потім переходити до /actuator/conditions.

4. Читаємо /actuator/conditions

Перше враження від /actuator/conditions зазвичай таке: «це що, весь інтернет у JSON?» І це нормальна реакція. Дані справді обʼємні, бо Spring Boot приймає багато рішень. Але читати їх «згори вниз» — це як намагатися навчитися готувати, зʼївши цілком кулінарну книжку. Непродуктивно і трохи небезпечно для психіки.

Правильна стратегія дуже проста: ви приходите в conditions з конкретним запитанням і шукаєте конкретний блок.

Можна тримати в голові такий алгоритм:

flowchart TD
  %% Алгоритм читання /actuator/conditions: рухаємося від запитання до конкретної конфігурації
  Q["Є запитання: «чому X?»"] --> F["Знаходимо пов'язану auto-configuration за назвою"]
  F --> P["Дивимося positive matches: чому увімкнулося"]
  F --> N["Дивимося negative matches: чому не увімкнулося"]
  P --> C["Звіряємо з профілем / properties / classpath"]
  N --> C
  C --> R["Робимо висновок: яку причину треба змінити"]

Ось приклади запитань, з якими conditions працює особливо добре:

Ви бачите, що застосунок віддає JSON, і хочете зрозуміти, завдяки якому механізму це «піднялося само». Ви додали Actuator і хочете переконатися, що саме він активний і які гілки конфігурації увімкнулися. Ви чекаєте якусь поведінку, але її немає — і хочете побачити, з якої причини конфігурація не спрацювала: немає класу, не те property чи конфлікт із вашим біном.

Технічно читати великий JSON найзручніше або в браузері, де є пошук по сторінці, або в IDE, якщо зберегти відповідь у файл. Для перших кроків достатньо банального Ctrl+F за назвою цікавої auto-configuration. Так, це не «суперінженерно», але зате чесно й працює у людей без ступеня з термінальних команд.

5. Мінікейси для catalog-service

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

Web runtime без ручного налаштування

У catalog-service ми використовуємо spring-boot-starter-webmvc. Це означає, що на classpath потрапляє Spring MVC, вбудований servlet-контейнер і набір стандартних бінів, щоб запити почали доходити до ваших @RestController. Але новачкові легко переплутати: він бачить лише контролер, а звідки взявся сервер — «незрозуміло, мабуть, Boot».

І ось тут conditions дає спокійну відповідь: є auto-configuration, що відповідає за MVC-шар, і вона увімкнулася, тому що на classpath є потрібні класи та тому що ви не оголосили жодних конфліктних налаштувань.

У реальному виводі ви побачите багато деталей, але концептуально це виглядає приблизно так, спрощений фрагмент, щоб не втонути:

{
  "positiveMatches": {
    "WebMvcAutoConfiguration": [
      // На classpath є ключові MVC-класи → можна вмикати MVC-шар
      "OnClassCondition: found DispatcherServlet and WebMvcConfigurer",
      // Користувач не перевизначив MVC повністю власною конфігурацією
      "OnMissingBeanCondition: no user-defined WebMvcConfigurationSupport"
    ]
  }
}

Сенс цього шматка не в тому, щоб запамʼятати точні назви умов, а в тому, щоб побачити механіку: Boot перевіряє «чи є класи, які означають MVC?», перевіряє «чи не взяли ви повний контроль над MVC вручну?», і якщо все гаразд — вмикає свою конфігурацію.

І ось тут зʼявляється дуже практичне застосування: якщо завтра ви випадково додасте щось, що змінює web-стек, наприклад інший starter, і застосунок почне поводитися інакше, conditions покаже, яка гілка стала активною і чому. Не «тому що Boot так захотів», а «тому що на classpath зʼявився клас X» або «тому що зʼявився бін Y».

Дуже важливий момент для спокою: поруч із цим ви майже напевно побачите негативні збіги для альтернативних реалізацій. Наприклад, конфігурації для іншого контейнера або іншого web-стеку. І це нормально: Boot знає про них, але не вмикає, бо у вас немає відповідних бібліотек.

Actuator: звідки беруться health/info

Коли ви підключили spring-boot-starter-actuator, ви додали до застосунку цілий діагностичний шар. І він теж вмикається не «за велінням небес», а через auto-configuration, яка бачить: на classpath є потрібні класи Actuator, отже можна реєструвати endpoints, health contributors та інші корисні речі.

У conditions ви зазвичай знайдете конфігурації, повʼязані з endpointʼами. У спрощеному вигляді логіка виглядає так:

{
  "positiveMatches": {
    "HealthEndpointAutoConfiguration": [
      // Клас endpoint-а присутній → можна реєструвати health endpoint
      "OnClassCondition: HealthEndpoint is present",
      // Налаштуваннями нічого не заборонено → endpoint увімкнено
      "OnPropertyCondition: endpoint enabled"
    ],
    "InfoEndpointAutoConfiguration": [
      // Те саме, але для info endpoint
      "OnClassCondition: InfoEndpoint is present",
      "OnPropertyCondition: endpoint enabled"
    ]
  }
}

У реальному сервісі ви можете зіткнутися із ситуацією: «чому endpoint є, але він 404?» І тут важливо не плутати дві різні речі. Endpoint може бути зареєстрований — це про умови вмикання auto-config, — але не експонований назовні — це вже про management.endpoints.web.exposure.*. Ми вже обговорили політику exposure; conditions допомагає зрозуміти першу частину (endpoint створено?), а вже потім ви перевіряєте, чи відкрили його назовні.

Знову невелике психологічне розвантаження: негативні збіги тут теж будуть. Наприклад, конфігурації для метрик, трасування та інших речей, що потребують додаткових залежностей. Вони можуть бути в negativeMatches не тому, що «зламано», а тому, що «у нашому курсі й у нашому проєкті це не підключено». І це, насправді, дуже хороша новина: ви бачите межі поточного стеку за фактами.

Бін лише в local-профілі

Тепер зробимо приклад не про автоконфіги Boot, а про нашу умовну реєстрацію. Так, conditions — про auto-configuration, але він дуже добре пояснює і ті речі, які ви робите через умови (@Profile, @ConditionalOnProperty тощо). Це особливо корисно, коли ви починаєте будувати «local-only» діагностику і хочете бути певні, що вона не витече в prod.

Припустімо, ми додали маленький marker-bean, який існує лише в профілі local. Він нічого не робить, але чудово демонструє умовну реєстрацію.

package com.example.catalogservice.config;

/**
 * Marker-клас для демонстрації умовної реєстрації біна.
 * Жодної логіки не містить: його завдання — просто бути присутнім у контексті.
 */
class LocalDiagnosticsMarker {
    // Порожній marker: його сенс — просто "існувати" в local-профілі
}

І конфігурація:

package com.example.catalogservice.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration // Говоримо Spring, що це конфігураційний клас (джерело @Bean-методів)
class LocalDiagnosticsConfiguration {

    @Bean // Реєструємо бін у контейнері Spring
    @Profile("local") // Бін з'явиться лише якщо активний профіль local
    LocalDiagnosticsMarker localDiagnosticsMarker() {
        // Повертаємо marker-об'єкт: він потрібен, щоб було видно, з'явився бін чи ні
        return new LocalDiagnosticsMarker();
    }
}

Якщо ви запускаєте застосунок із --spring.profiles.active=local, цей бін зʼявиться в контексті. Якщо запускаєте без профілю — зникне.

Де тут conditions? Він корисний тим, що дозволяє побачити: бін або конфігурація не зʼявилися не «тому що Spring не побачив файл», а тому що умова профілю не збіглася. У виводі це виглядатиме як «збіглося / не збіглося» за умовою ProfileCondition. І саме це повертає нас до інженерної картини: результат визначається правилами, а не настроєм фреймворку.

До речі, цей приклад у реальному проєкті можна легко повʼязати з діагностикою Actuator: ви можете тримати розширену експозицію endpoints лише в local, а в prod залишити мінімум. І conditions буде одним із тих endpoints, які ви тримаєте в local виключно як інструмент розробника.

6. Типові помилки під час роботи з conditions

Помилка №1: читати вивід цілком, зверху вниз, як роман.
Вивід conditions великий, бо рішень у Boot багато. Якщо намагатися «прочитати все», мозок швидко переходить у режим «нічого не розумію». Набагато продуктивніше прийти із запитанням, знайти конкретну auto-configuration за назвою і вивчити лише її гілку: чому увімкнулася або чому не увімкнулася.

Помилка №2: сприймати будь-який negativeMatches як поломку.
У застосунку завжди буде безліч гілок, які не мають спрацьовувати. Наприклад, конфігурації для технологій, яких у вас немає в залежностях. Негативний збіг — це не діагноз, а пояснення: «ця гілка не підходить під поточний classpath / properties / біни». Помилкою це стає лише тоді, коли ви очікували увімкнення цієї гілки.

Помилка №3: забувати, що “увімкнено” і “експоновано” — різні речі.
Дуже часта ситуація з Actuator: endpoint у застосунку є (автоконфігурація його зареєструвала), але зовні він недоступний, тому що ви не ввімкнули його в management.endpoints.web.exposure.include. conditions допомагає зрозуміти першу частину (endpoint створено?), а exposure-налаштування — другу (endpoint відкрито назовні?).

Помилка №4: ігнорувати профілі та властивості під час інтерпретації результату.
Conditions майже завжди завʼязані на три речі: classpath, активні profiles і properties. Якщо ви дивитеся conditions, але не звіряєте його з тим, які профілі активні й які значення властивостей реально спрацювали, ви легко зробите хибний висновок. У нашому курсі для цієї звірки вже є знайомі інструменти: логи старту, env і configprops.

Помилка №5: намагатися знайти в conditions пояснення бізнес-помилок.
Endpoint conditions відповідає на запитання «чому інфраструктура зібралася так», а не «чому ваш сервіс повернув неправильний список курсів». Якщо у вас помилка у фільтрації, в in-memory репозиторії або в контролері — conditions не має її «пояснити». Він допоможе лише переконатися, що потрібні інфраструктурні частини взагалі увімкнені, і далі ви йдете в код і логи.

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