JavaRush /Курсы /Spring Boot /Condition evaluation delta в Boot

Condition evaluation delta в Boot

Spring Boot
19 уровень , 3 лекция
Открыта

1. Назначение condition evaluation delta

Когда у вас включён DevTools, вы начинаете жить в режиме «поправил → сохранил → посмотрел результат». Это прекрасно, пока всё очевидно. Но как только вы меняете что-то инфраструктурное (бин, профиль, конфиг), рестарт происходит быстро, а вот понять причину изменения поведения — медленно. Condition evaluation delta — это попытка Spring Boot дать вам «git diff», но не для файлов, а для решений auto-configuration: что считалось подходящим до рестарта и что стало подходящим после.

Чтобы почувствовать проблему, представьте типичную ситуацию: вы добавили крошечный @Bean, а после рестарта внезапно «куда-то пропало» дефолтное поведение Boot. Логи большие, вы проматываете, находите кучу строк про Tomcat, MVC, JSON, и всё равно не видите объяснения. Delta как раз и делает эту историю человеческой: она не пересказывает вам весь старт, а показывает только изменения в условных матчах между двумя состояниями.

Важно понимать тонкий момент: delta — это не про «какие бины созданы» (хотя косвенно там часто видно следствие), а про то, какие условия стали true/false и почему Boot включил или выключил кусок авто-конфигурации. То есть это инструмент не столько для скорости, сколько для понимания.

2. Что сравнивает DevTools

Чтобы delta не выглядела как очередная загадочная портянка, полезно держать в голове простую модель. У Boot есть набор auto-configuration классов, и каждый такой класс похож на «пакет настроек», который включается только если выполняются условия. Условий несколько типов, но на практике чаще всего вы будете встречать три: проверка наличия класса на classpath, проверка наличия бина в ApplicationContext и проверка значения property. Из этих условий Boot собирает ответ на вопрос: «включаем это или нет?».

Внутри Boot есть сводка, которая аккуратно записывает: какая авто-конфигурация сработала, какая не сработала, и по какой причине. Эту сводку обычно называют Condition Evaluation Report — по смыслу это отчёт о том, как условия сработали. DevTools берёт отчёт «до», берёт отчёт «после» и печатает разницу — delta. Это именно diff: что было в прошлом состоянии, чего не стало; и что появилось в новом.

Наглядно это можно представить так:

flowchart TD
    A[Вы меняете код/конфиг] --> B[DevTools делает restart]
    B --> C[Boot собирает Condition Evaluation Report]
    C --> D[DevTools сравнивает: прошлый report vs новый report]
    D --> E[В логах появляется CONDITIONS EVALUATION DELTA]
    E --> F[Вы понимаете: какое условие изменилось и почему]

И здесь сразу полезная инженерная мысль: delta отвечает не на вопрос «что сломалось», а на вопрос «какое решение Boot изменилось». Это иногда даже важнее ошибок, потому что ошибка — событие, а смена решения — причина.

3. Включение и поиск delta в логах

У delta есть одна очень практическая особенность: она появляется после рестарта, а не «каждую секунду». То есть если вы поменяли статический HTML и получили reload, никакой delta вы не увидите — потому что условия auto-configuration заново не пересчитывались, контекст не пересоздавался. Delta — история про restart и только про restart. Поэтому для неё важны ровно те изменения, которые реально влияют на ApplicationContext и его сборку.

В логах delta обычно выделяется очень заметным заголовком. В реальности вы будете искать глазами блок примерно такого вида (формат может отличаться, но смысл одинаковый):

============================
CONDITIONS EVALUATION DELTA
============================

Если delta начинает раздражать (например, вы делаете много маленьких правок и вам не хочется постоянно видеть этот блок), её можно отключить настройкой DevTools. Самый аккуратный вариант — держать это в локальном профиле, чтобы общая конфигурация проекта не превращалась в свалку developer convenience:

# src/main/resources/application-local.yaml
spring:
  devtools:
    restart:
      log-condition-evaluation-delta: false

Если захотите вернуть обратно, просто поставьте true или удалите ключ — и вернётесь к поведению по умолчанию. Самое главное: не превращайте это свойство в религию. Delta — полезный прожектор, но иногда он светит прямо в глаза.

4. Чтение delta: что изменилось и почему

Читать delta как «ещё один стартовый лог» — почти гарантированно бессмысленно. Рабочий подход другой: сначала вы формулируете, что конкретно вы изменили, а потом в delta ищете именно тот тип условий, который должен был отреагировать. Если вы добавили @Bean, логично ожидать изменения условий вида @ConditionalOnMissingBean. Если вы переключили профиль, логично ожидать, что поменяются property-условия или просто наличие ваших конфигурационных классов. Если вы изменили только бизнес-логику в сервисе, delta может быть пустой — и это нормально.

Полезно держать в голове эту мини-таблицу «изменение → что чаще всего двигается в delta»:

Что вы поменяли Что обычно меняется в delta Какой вывод можно сделать
Добавили @Bean в проекте @ConditionalOnMissingBean перестал матчиться у auto-config Boot отступил, вместо дефолта взял вашу настройку
Убрали ваш @Bean Раньше auto-config не матчился, теперь матчится Boot вернулся к дефолтам
Переключили profile (localdev) Условия, завязанные на свойства или профиль, поменяли исход Профиль реально активировался или нет, а не «кажется»
Изменили YAML-свойство, влияющее на условия @ConditionalOnProperty поменял матч Свойство реально читается и имеет ожидаемое значение

Ещё один важный момент: delta не обязательно означает «плохо». В ней могут быть Negative matches, и это вообще не ошибка. Это часто просто констатация: «раньше условие было true, а теперь false, потому что вы добавили бин». В хорошем Boot-проекте такое происходит постоянно, и это нормальная часть управляемой кастомизации.

5. Сценарий: добавили свой bean — Boot «отступил»

Самый классический случай, когда delta реально спасает время: вы добавили свой инфраструктурный бин (осознанно или случайно), и часть auto-configuration перестала включаться, потому что Boot работает по принципу «если вы явно настроили — я не мешаю». Это и есть backoff-модель, которую мы уже обсуждали раньше. Проблема в том, что без delta вы иногда не понимаете, какой именно дефолт Boot перестал применять и почему.

Возьмём catalog-service и сделаем намеренно учебный эксперимент: временно добавим свой ObjectMapper (или, если точнее, JsonMapper, который является наследником ObjectMapper), чтобы увидеть backoff-модель в delta. Сразу предупреждение в стиле «чтобы потом не было больно»: держать такой override как постоянную локальную настройку не нужно. Нам здесь важен не сам маппер, а то, как delta показывает изменение решения Boot.

Добавим конфигурацию, активную только в local профиле, чтобы эксперимент был контролируемым:

package com.example.catalogservice.support.jackson;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration // Подхватывается component scan: это обычная Spring-конфигурация
@Profile("local") // Включаем эту конфигурацию только в локальном профиле, чтобы не унести эксперимент дальше
public class LocalJacksonConfiguration {

    @Bean // Создаём свой ObjectMapper: этим мы заставляем Boot "отступить" от дефолтной JacksonAutoConfiguration
    public ObjectMapper objectMapper() {
        // Собственный маппер, чтобы увидеть эффект backoff-модели в delta
        return JsonMapper.builder()
                .findAndAddModules() // Автоматически подключаем модули (например, JavaTimeModule для java.time.*)
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // Даты будут сериализоваться более "человечно"
                .build(); // Собираем готовый ObjectMapper
    }
}

Обратите внимание на два момента. Во-первых, класс лежит внутри корневого пакета приложения (com.example.catalogservice...), значит, он попадёт в component scan. Во-вторых, @Profile("local") делает этот бин «включаемым по переключателю»: в local он есть, в dev и prod — нет.

После сохранения файла DevTools сделает restart (потому что это изменение Java-кода), и в логах вы увидите delta. В учебном виде она может выглядеть так:

============================
CONDITIONS EVALUATION DELTA
============================

Negative matches:
----------------
JacksonAutoConfiguration:
   Did not match:
      - @ConditionalOnMissingBean (types: com.fasterxml.jackson.databind.ObjectMapper)
        found bean 'objectMapper' (OnBeanCondition)

Смысл читается буквально: «JacksonAutoConfiguration больше не подходит, потому что условие @ConditionalOnMissingBean(ObjectMapper) не выполняется — бин objectMapper уже есть». И это именно то, чего мы хотели добиться как учебного эффекта: Boot честно говорит, почему он не создаёт свой дефолтный ObjectMapper.

Есть и очень жизненный взрослый вывод. Если вы когда-нибудь добавили ObjectMapper и внезапно у вас изменился формат дат или сериализация enum’ов — delta часто даёт первое объяснение, где именно «переключился рычаг». И да, это тот случай, когда одна маленькая конфигурация может поменять поведение всего JSON-слоя. Поэтому backoff-модель — мощный инструмент, но и ответственность на вас тоже реальная. Как только вы увидели этот эффект, LocalJacksonConfiguration лучше убрать, чтобы локальный профиль снова жил на обычной JSON-конфигурации Boot.

6. Сценарий: смена профиля

Профили в Boot — вещь прекрасная, но коварная. Прекрасная, потому что позволяют держать локальные удобства отдельно. Коварная, потому что иногда вам кажется, что активен local, а на самом деле стартанул default, и вы полчаса спорите с монитором, почему «настройка не работает». Condition evaluation delta в таких случаях — почти как свидетель: показывает, что включилось, а что выключилось, именно из-за смены условий.

Продолжим наш пример с LocalJacksonConfiguration. В local профиле бин objectMapper есть, и Boot отступает от своей JacksonAutoConfiguration. Как только вы уходите из local или просто убираете этот временный бин, при следующем рестарте delta должна показать обратную картину: авто-конфигурация снова матчится, потому что missing bean снова true.

С точки зрения логики это выглядит так: в dev профиле нет вашего ObjectMapper → условие @ConditionalOnMissingBean(ObjectMapper) снова выполняется → Boot снова включает свою авто-конфигурацию.

Условный фрагмент delta может стать таким:

============================
CONDITIONS EVALUATION DELTA
============================

Positive matches:
----------------
JacksonAutoConfiguration:
   Matched:
      - @ConditionalOnMissingBean (types: com.fasterxml.jackson.databind.ObjectMapper)
        did not find any beans (OnBeanCondition)

Теперь delta превращается в очень конкретный сигнал: «в этом запуске Boot снова считает, что ObjectMapper отсутствует, значит, он создаст дефолтный». И это полезно не только для Jackson. Точно так же вы можете отлавливать профильные изменения в MVC-настройках, в условных enabled/no-op бинax и в любых местах, где поведение зависит от окружения.

И снова важный нюанс: delta не заменяет вам понимание профилей, но сильно сокращает путь от «кажется, не тот профиль» к «да, реально не тот профиль». В обучении это экономит нервы, а в работе — часы. А сам временный override после такой проверки лучше убрать, чтобы local не тащил лишнюю JSON-кастомизацию.

7. Delta и framework takeover в MVC

Иногда студенты, и честно говоря не только студенты, случайно устраивают Spring Boot проекту маленький переворот. Самый частый сценарий — кто-то добавляет слишком «сильную» MVC-кастомизацию, и Boot начинает отключать часть своих дефолтов. Мы в курсе специально держим границу: кастомизируем MVC через WebMvcConfigurer, но не включаем @EnableWebMvc, потому что это часто превращается в framework takeover. И delta — один из инструментов, который помогает увидеть этот takeover не по симптомам («почему вдруг не работают статические ресурсы?»), а по причине («почему отключилась авто-конфигурация?»).

Даже если вы не планируете делать такие эксперименты, полезно понимать принцип. Auto-configuration класса WebMvcAutoConfiguration обычно имеет условие вида: «включайся, если никто не принёс свою полную MVC-конфигурацию». Когда вы приносите слишком тяжёлую настройку, Boot видит это и отступает. Delta в таких случаях часто показывает резкое движение: большая авто-конфигурация ушла в negative matches из-за OnBeanCondition.

И вот почему это важно методически. Когда вы видите в delta, что целый пласт auto-configuration перестал матчиться, это почти всегда повод остановиться и спросить себя: «я действительно хотел отключить дефолты Boot, или я просто хотел добавить маленький форматтер или конвертер?» В нашем курсе правильный ответ обычно второй. И delta помогает поймать момент, когда вы случайно сделали первое.

8. Дисциплина чтения delta

Delta — штука информативная, но у неё есть режим болтливости, особенно если вы сделали изменения, которые затронули много auto-configuration классов. В такие моменты легко скатиться в чтение лога как романа: «Positive matches… Negative matches…» и через пять минут вы понимаете, что ничего не поняли, но очень устали. Чтобы delta работала на вас, а не против вас, ей нужна дисциплина.

Первый приём — не читать delta сверху вниз, а искать знакомые слова. Почти всегда вы уже знаете область изменения: JSON, MVC, profiles, какие-то ваши @Configuration классы. Значит, вы ищете в delta соответствующие имена авто-конфигураций или типы условий. Второй приём — помнить, что delta показывает разницу между двумя состояниями. Если вы не помните, что именно сделали перед рестартом, delta будет выглядеть как магия, потому что вы потеряли вторую половину диффа — «до».

Третий приём — разрешить себе выключать delta временно, когда вы работаете над задачей, где она не помогает. Например, вы правите чистую бизнес-логику фильтрации курсов в CourseCatalogService, и каждый рестарт сопровождается delta, которая по факту пустая или не про то. Выключили — сделали работу — включили обратно. Это не слабость. Это нормальная настройка инструментов под задачу, а не задача под инструменты.

9. Типичные ошибки при работе с delta

Ошибка №1: читать delta как лог ошибок и пугаться слова «Negative».
В delta negative matches чаще всего означает не проблему, а факт: эта auto-configuration теперь не применяется, потому что условие не выполняется. В Boot это нормальная часть механики backoff. Ошибка начинается, когда вы воспринимаете любой negative как поломку и начинаете чинить то, что вообще не сломано.

Ошибка №2: пытаться понять delta, не зафиксировав, что именно вы поменяли.
Delta — это дифф. Если вы не помните «левую сторону» — предыдущее состояние, — «правая сторона» превращается в набор загадочных строк. Это особенно часто происходит, когда вы сделали несколько изменений подряд: подправили YAML, добавили бин, переключили профиль, и потом пытаетесь читать delta как будто это одна причина. В таком режиме она не помогает.

Ошибка №3: ожидать, что delta будет реагировать на каждое изменение файла.
Delta появляется после restart, а не после reload. Если вы поменяли статический ресурс или что-то, что DevTools обновил без пересоздания контекста, delta не обязана появляться. Иногда студенты думают «delta не работает», хотя на самом деле не было рестарта. Это не баг — это логика инструмента.

Ошибка №4: делать «учебный» инфраструктурный override и забыть, что он влияет на весь сервис.
Пример с ObjectMapper очень показательный: вы добавили один бин — и у вас изменился весь JSON-слой. Delta в этом случае честно говорит «Boot отступил», но она не спасёт от последствий, если вы не понимаете, что именно вы перехватили. Поэтому любые overrides инфраструктуры нужно делать либо очень аккуратно, либо строго ограничивать профилем @Profile("local"), чтобы случайно не унести эксперимент туда, где он не нужен.

Ошибка №5: выключать delta слишком рано «потому что много текста».
Да, delta может быть шумной. Но часто она становится понятной ровно после пары раз осознанного чтения. Если отключить её при первом же дискомфорте, вы теряете один из самых удобных мостов между «я что-то поменял» и «Boot принял другое решение». Здесь лучше не геройствовать, но и не капитулировать: почитайте её на одном-двух простых кейсах, и дальше она начнёт работать как подсказка, а не как наказание.

1
Задача
Spring Boot, 19 уровень, 3 лекция
Недоступна
Delta после добавления собственного ObjectMapper
Delta после добавления собственного ObjectMapper
1
Задача
Spring Boot, 19 уровень, 3 лекция
Недоступна
Delta при переключении property
Delta при переключении property
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ