JavaRush /Курсы /Spring Boot /Exclusions: отключаем auto-configuration

Exclusions: отключаем auto-configuration

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

1. Exclusion — это не «настройка», а выключатель

Обычно Junior-разработчик начинает кастомизацию Boot с внутренней фразы: «Кажется, он делает какую-то магию… выключу-ка я её». И рука тянется к exclude, потому что выглядит просто: написал одну строчку — и «лишнее» исчезло. Проблема в том, что exclude — это не косметика, а топор.

Важная мысль: exclusion отключает не конкретный bean, а целый auto-configuration класс. А auto-configuration класс — это, по сути, «пачка решений»: несколько @Bean-методов, условия, иногда импорты других конфигураций. То есть вы выключаете не лампочку, а выдёргиваете предохранитель, который питает половину комнаты. В бытовом смысле это иногда очень полезно. В инженерном — это всегда риск, потому что вы меняете сборку приложения грубым движением.

После точечной подмены одного bean и маленьких тематических @Configuration естественно возникает следующий по силе инструмент — exclusion. К нему стоит тянуться не тогда, когда раздражает один компонент, а тогда, когда целый auto-config блок правда не должен участвовать в сборке приложения.

В контексте нашего catalog-service это выглядит так. Если мы хотим заменить один инфраструктурный компонент (например, форматтер стартового сообщения или условный «репортёр старта»), почти всегда правильнее сделать это через собственный bean или через property-condition. Exclusion же становится оправданным только тогда, когда вы можете честно сказать: «Этот целый блок поведения нам вообще не нужен, и мы точно понимаем последствия».

2. Что происходит в Boot при exclude

Чтобы не воспринимать exclude как заклинание, полезно представлять, в какой момент Boot его применяет. И здесь хорошая новость: никакой мистики, просто этап сборки. Boot сначала собирает список кандидатов на автоконфигурацию, а потом решает, какие из них реально включать, исходя из условий. Exclusion вмешивается очень рано — ещё до того, как условия начинают работать.

На «человеческом» уровне это можно представить так:

flowchart TD
    A["Classpath + imports-файл
список auto-config кандидатов"] --> B["Применяем exclude
(аннотация и/или свойства)"] B --> C["Оцениваем условия
@ConditionalOnClass/@ConditionalOnProperty/..."] C --> D["Регистрируем @Bean
в ApplicationContext"]

То есть exclusion — это команда: «Не рассматривай этот auto-config вообще». В результате для исключённого класса не выполняются даже его условия (они просто не будут проверяться), и его @Bean-методы не попадут в контекст.

Это важно по двум причинам. Во‑первых, когда вы исключаете auto-config, вы теряете не только конкретный неудобный bean, но и все остальные, которые этот класс мог создать «заодно». Во‑вторых, другие auto-config классы часто пишутся с логикой «если в контексте есть X, то настроим Y». Если вы выключили блок, который создавал X, то Y может тоже не подняться. Причём не обязательно с ошибкой — иногда просто «тихо не включится».

Поэтому exclusion нельзя воспринимать как «удалить один лишний компонент». Это больше похоже на «убрать целую коробку деталей из набора LEGO, а потом удивляться, почему крыша не держится».

3. exclude, свой bean и @ConditionalOnProperty

Перед тем как вообще обсуждать «когда оправдано выключать», полезно выстроить в голове шкалу силы инструментов. У Spring Boot есть несколько способов повлиять на поведение, и они отличаются масштабом воздействия. Если выбрать слишком сильный инструмент для маленькой задачи, вы получите хрупкий проект, который сложно объяснять и поддерживать.

Сравним три самых частых рычага (в таблице — без фанатизма, на уровне курса):

Инструмент Масштаб воздействия Типичный сценарий Главный риск
Свойство / флаг (обычно через @ConditionalOnProperty или стандартные spring.*) Узкий, «включить/выключить фичу» Поведение должно переключаться без перекомпиляции Разрастание конфигов, если делать флаги «на всё подряд»
Свой bean вместо default (@ConditionalOnMissingBean) Средний, «заменить конкретный компонент» Хотим своё поведение, но остаёмся Boot-friendly Ошибиться типом/смыслом bean и не получить ожидаемый эффект
Exclusion auto-config класса (exclude) Широкий, «выключить целый блок» Подсистема не нужна, мешает или конфликтует Сломать зависимые настройки, замаскировать реальную проблему

Если перевести это в простое правило (без списков, но с идеей): начинайте с самого маленького вмешательства. Если задачу можно решить свойством — решайте свойством. Если нужно заменить один компонент — создайте свой bean. И только если вы честно понимаете, что целый блок автоконфигурации вам не нужен, тогда exclude становится вариантом.

Небольшая «блок-схема выбора» тоже помогает голове не паниковать:

flowchart TD
    A["Мне не нравится поведение Boot"] --> B{"Это один bean
или целый блок?"} B -->|Один bean| C{"Можно объявить свой bean?"} C -->|Да| D["Создаём свой bean
Boot отступает"] C -->|Нет| E["Ищем property/настройку"] B -->|Целый блок| F{"Блок реально не нужен
и мы понимаем последствия?"} F -->|Нет| G["Сначала разберись, что включилось
(зависимость? условие?)"] F -->|Да| H["Делаем exclude
точечно и осознанно"]

В этой лекции мы как раз про нижнюю ветку — когда блок реально не нужен.

4. Exclusion в реальной жизни: три сценария

Сейчас будет важный момент: exclusion — это не «плохая практика» сама по себе. Это просто мощный инструмент. Проблемы начинаются, когда его применяют не по назначению. Чтобы у вас не было ощущения «ну тогда вообще нельзя», давайте разберём несколько сценариев, где exclusion действительно встречается в реальной жизни — и выглядит логично даже для начинающего.

Сценарий A: в проект случайно приехала подсистема

Классика жанра: вы добавили зависимость «ради одной мелочи», а вместе с ней приехала целая подсистема. Boot её увидел на classpath, условия совпали — и автоконфигурация начала пытаться поднять инфраструктуру, которая вашему сервису вообще не нужна.

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

Failed to configure a DataSource: 'url' attribute is not specified
and no embedded datasource could be configured.

Для catalog-service это особенно показательно, потому что по ТЗ проекта у нас нет базы данных. Значит, если Boot вообще начал пытаться настраивать DataSource, это почти наверняка симптом того, что в dependency tree появились JDBC/JPA-зависимости.

Что делать правильно? В идеальном мире — вернуться в build.gradle.kts и разобраться, кто привёз эту зависимость, и удалить/заменить её. Но бывают ситуации, когда вы не можете легко убрать библиотеку (например, она нужна ради другого функционала, а JDBC прилетело транзитивно). И вот тогда exclusion становится «временным заглушающим решением», чтобы сервис хотя бы стартовал.

Пример (показательный, не значит «делайте так всегда»):

package com.example.catalogservice;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) // Отключаем целую автонастройку источника данных
public class CatalogServiceApplication {
    // Важно: это НЕ "удалить один бин", а вырубить весь блок, который поднимает DataSource и связанное с ним поведение.
    // Держите в голове вопрос: почему JDBC вообще оказался на classpath в сервисе без БД?
}

Да, это может разблокировать старт. Но если вы поймали себя на мысли «о, удобно, оставлю навсегда», стоит остановиться и спросить: а почему в проекте без БД вообще появился JDBC-слой? В большинстве случаев лучше лечить причину, а exclusion считать костылём, который вы себе честно признаёте костылём.

Сценарий B: подсистема не нужна целиком

Иногда подсистема действительно не нужна целиком, и это не ошибка зависимости, а архитектурное решение. Например, вы подключили внешний стартер/модуль, а он добавил auto-config блок, который вам не подходит (или дублирует то, что вы делаете в своём коде). Вы не хотите ни одного бина из этого блока, потому что он в принципе про «чужую политику».

Для учебного примера можно представить, что у нас есть auto-config, который добавляет «репорт старта приложения» (условно), но в нашем сервисе мы решили, что этот механизм вообще не нужен и мы не хотим даже видеть его бины в контексте.

Тогда исключение выглядит честно: «этого блока в сборке приложения быть не должно».

package com.example.catalogservice;

import com.example.catalogservice.support.startup.StartupReportAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(exclude = StartupReportAutoConfiguration.class) // Полностью убираем чужую "политику" из контекста
public class CatalogServiceApplication {
    // Здесь исключение логично, только если действительно НЕ нужен ни один бин из этого auto-config класса.
}

Здесь важная оговорка: это решение имеет смысл только если вы точно понимаете, что отключаете целиком, и не ожидаете частичного поведения. Если вам нужно «включать/выключать репорт» по флагу — это история для @ConditionalOnProperty, а не для exclude.

Сценарий C: аварийный запуск

Звучит как оправдание, но в реальных проектах встречается постоянно. Бывает, что приложение должно запускаться (хотя бы локально), чтобы вы могли посмотреть логи, проверить wiring или просто не блокировать команду. А auto-config ломает старт по причине, которую вы пока не можете быстро исправить (например, нет доступа к окружению, не готовы secrets, или «на этом ноутбуке сегодня не будет VPN»).

В таком случае exclusion может быть временной «кнопкой аварийного запуска». Но здесь очень легко скатиться в привычку: «просто исключим ещё вот это, и ещё вот то — и всё заработает». Это уже прямой путь к framework takeover (об этом мы поговорим позже в дне), когда Boot перестаёт быть платформой, и вы вручную собираете всё подряд.

Поэтому если вы используете exclusion как временную меру, полезно держать дисциплину: один exclusion — одна причина — и желательно маленький комментарий рядом, чтобы через две недели вы не гадали, что это было.

5. Отключение auto-configuration: аннотация и свойство

Когда мы говорим «сделать exclusion», технически есть несколько способов. Для Junior-разработчика важно не запомнить все варианты, а понять главный: где это пишется, как читается, и почему должно быть максимально заметно. Потому что exclusion влияет на сборку приложения — это как изменить схему электрики: вы хотите, чтобы это было видно сразу.

Вариант 1: exclude на @SpringBootApplication

Чаще всего exclusion пишут прямо на main-классе. Это логично: main-класс — точка сборки приложения. Там уже живёт @SpringBootApplication, а значит там же самое заметное место для «мы выключаем блок автоконфигурации».

package com.example.catalogservice;

import com.example.catalogservice.support.startup.StartupSupportAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(exclude = StartupSupportAutoConfiguration.class) // Выключаем блок автонастройки целиком
public class CatalogServiceApplication {

    public static void main(String[] args) {
        // Это точка старта приложения: если выключатель стоит здесь — его сразу видно.
        SpringApplication.run(CatalogServiceApplication.class, args);
    }
}

Плюс этого варианта — читаемость. Открыл main-класс и сразу увидел: «ага, вот эта auto-config отключена».

Вариант 2: spring.autoconfigure.exclude в конфигурации

Иногда нужно отключить auto-config не через код, а через конфигурацию. Например, вы хотите быстро проверить гипотезу «что будет, если отключить блок», или вы не хотите пересобирать jar ради эксперимента. Тогда можно использовать свойство spring.autoconfigure.exclude.

В YAML это выглядит так:

spring:
  autoconfigure:
    exclude:
      # То же самое, что и exclude в @SpringBootApplication, но "спрятано" в конфиге.
      # Удобно для экспериментов и временных обходных путей без пересборки.
      - com.example.catalogservice.support.startup.StartupSupportAutoConfiguration

Этот способ работает, но у него есть тонкость: настройка становится «менее заметной». Если кто-то смотрит только Java-код, он может не понять, почему часть поведения «пропала». Поэтому как постоянное решение для приложения я предпочитаю аннотацию в main-классе, а конфигурационный exclusion — как временный инструмент или как очень осознанную стратегию.

Важное уточнение: это не Gradle exclude

Слово exclude встречается и в Gradle, но это другая вселенная. Gradle exclude — это про зависимости (что попадёт на classpath). Spring Boot exclude — это про автоконфигурацию (что будет включено в контекст). Иногда правильное решение — исключить зависимость, а не исключать auto-config. И, честно говоря, в половине случаев «exclusion auto-config» — это попытка исправить то, что нужно было исправить на уровне dependency management.

6. Exclusion как симптом: сначала зависимость, потом выключатель

На этом месте обычно хочется дать универсальный рецепт, но правильнее дать дисциплину мышления. Spring Boot включает auto-config не потому, что он «вредный», а потому что он видит на classpath нужные библиотеки и честно пытается помочь. Если помощь стала проблемой, часто проблема не в Boot, а в том, что вы случайно принесли не ту технологию.

Для catalog-service это особенно важно, потому что проект по ТЗ лёгкий: без базы, без security, без тяжёлых интеграций. Поэтому если внезапно появилась автонастройка DataSource или security-фильтров, это почти наверняка означает: вы или коллега добавили зависимость «не туда».

Здесь хорошо работает старое инженерное правило: если у вас загорается лампочка «Check Engine», можно, конечно, заклеить её изолентой (это будет наш exclusion), но лучше всё-таки открыть капот. В нашем курсе «открыть капот» — это вспомнить навыки работы с dependency tree: посмотреть, какой стартер/библиотека привезла лишний модуль, и убрать проблему на уровне зависимостей. Exclusion стоит применять тогда, когда вы уже проверили classpath и всё равно осознанно решили: «да, модуль остаётся, но его автоконфигурация нам не нужна».

И ещё один нюанс: иногда Boot даёт официальный «мягкий выключатель» через свойства. Если вам нужно отключить часть поведения, ищите сначала property. Exclusion — это когда мягкого выключателя нет или он вам не подходит, потому что вы хотите убрать блок целиком.

Проверка: exclusion не ломает приложение

После exclusion нельзя просто сказать «компилируется — значит ок». В Boot иногда всё компилируется, но ломается на старте (и это нормально: wiring и условия проверяются в runtime). Поэтому после того, как вы выключили блок, нужно хотя бы минимально убедиться, что приложение стартует и что вы получили именно то поведение, которое ожидали.

Самый частый результат «неудачного exclusion» — ошибка отсутствующего бина. Например, у нас был компонент, который зависел от default-bean, а мы выключили auto-config, который этот bean создавал. Тогда старт упадёт примерно так (сокращённо):

Parameter 0 of constructor in ...StartupSummaryRunner required a bean of type
'StartupMessageFormatter' that could not be found.

Это, кстати, хороший сценарий для обучения: он показывает, что exclusion — не «удаление лишнего», а изменение сборки. Если вы хотите выключить auto-config, но оставить приложение рабочим, вам нужно либо предоставить альтернативный bean (это путь лекции 1), либо изменить зависимость на optional (мы это обсуждали в wiring-теме), либо признать, что компонент больше не нужен.

В catalog-service удобно держать простую дисциплину: если вы добавили exclusion, вы должны уметь одной фразой ответить на три вопроса. Какой блок выключили, почему выключили, что осталось работать по умолчанию. Если на один из вопросов ответ получается в стиле «ну… чтобы не мешало», значит решение пока сырое.

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

Перед тем как закрыть тему exclusions, полезно проговорить несколько ошибок, которые встречаются у начинающих почти гарантированно. Они не про «кривые руки», а про естественное желание упростить себе жизнь и выключить то, что не понимаешь. Boot на это реагирует предсказуемо: иногда молчит, иногда ломается, а иногда «работает, но странно» — и это самый неприятный вариант.

Ошибка №1: cargo-cult exclusion («взял из интернета и вставил»).
Это ситуация, когда разработчик не понял причину проблемы, но нашёл в чужом репозитории строчку exclude и просто скопировал. Опасность здесь в том, что вы выключаете блок, который мог давать не только «нежелательное», но и полезное. В итоге приложение стартует, но дальше ломается в другом месте, и вы начинаете наращивать список exclusions как снежный ком.

Ошибка №2: exclusion для замены одного-единственного bean’а.
Очень частая история: «Мне не нравится конкретный бин, значит выключу весь auto-config класс». Это как выбросить весь холодильник, потому что вам не понравился один йогурт. Если цель — заменить один компонент, чаще всего правильнее объявить собственный bean правильного типа и позволить Boot «отступить» через @ConditionalOnMissingBean.

Ошибка №3: длинный список exclusions в main-классе.
Один exclusion ещё может быть осознанным решением. Десять exclusions — почти всегда признак, что вы уже не используете Boot как платформу, а вручную пересобираете его слой. В такой конфигурации вы теряете главную ценность Boot: curated defaults и предсказуемую автосборку. Дальше будет только хуже: каждое обновление зависимостей начнёт «стрелять» неожиданными побочками.

Ошибка №4: путаница между exclusion в Boot и exclusion в Gradle.
Иногда разработчик пытается решить dependency-проблему через @SpringBootApplication(exclude=...), хотя нужно было убрать лишнюю библиотеку из classpath. Это приводит к странной картине: библиотека остаётся в проекте, но её автонастройка выключена, и часть кода может начать работать в «полусобранном» режиме. Если проблема началась после добавления зависимости — сначала проверьте dependency tree, а уже потом думайте об exclude.

Ошибка №5: exclusion через конфигурацию и забыли, что он вообще есть.
Когда exclusion задан через spring.autoconfigure.exclude в YAML, он легко теряется из виду. Потом вы смотрите на Java-код и удивляетесь: «почему Boot не создал default-bean?». Поэтому если это не временный эксперимент, лучше держать exclusions максимально явно, рядом с main-классом, или хотя бы документировать причину (пусть даже коротким комментарием).

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