Это довольно короткая статья, целью которой является пояснение того, что вообще такое «модуляризация» Spring Boot, почему она появилась и откуда, собственно, ноги растут.

Для многих Spring Boot это просто автоконфигурация. Само собой Spring Boot гораздо шире и включает в себя в том числе ещё и

  • Spring Boot Actuator

  • Spring Boot DevTools

  • Spring Boot Tools и так далее

Тем не менее, автоконфигурация это действительно то, что первое приходит на ум, когда человека на собеседовании спрашивают:

Что такое Spring Boot и чем он отличается от Spring Framework?

Если, условно, 15 лет назад человек для работы с базой данных делал нечто подобное (очень грубо):

@Configuration
public class DatabaseConfig {

  @Bean
  public DataSource dataSource() {
    HikariDataSource dataSource = new HikariDataSource();
    // Configuring data source...
    return new dataSource;
  }
}

То с появлением Spring Boot достаточно просто сделать вот так:

spring:
  datasource:
    url: jdbc:<db_vendor>://<host>:<port>/database?<connection_options>
    username: <username>
    password: <password>

И уже где-то внутри Spring Boot-а находится @ConfigurationProperties, в нашем случае DataSourceProperties, на который будет смаплена данная конфигурация, и Spring Boot как-то сам создаст такой DataSource, который посчитает нужным (P.S.: Для любознательных - вот тут можете посмотреть собственно как это происходит и какие DataSource-ы Spring Boot поддерживает из коробки).

Ну вот всё же зашибись, в чём же проблема?

Встречайте. Spring Boot AutoConfigure

А дело вот в чём. Дело в том, что в Spring Boot довольно давно существовал огромный модуль - spring-boot-autoconfigure. Этот модуль исторически хранил все автоконфигурации для вообще всех технологий, что Spring Boot поддерживал. От Apache Cassandra, до Apache Pulsar. Можете прямо посмотреть как выглядел build.gradle в Spring Boot 3 у spring-boot-autoconfigure.

Теперь смотрим далее. Само собой, из всех возможных технологий, которые поддерживает Spring Boot, у конечного пользователя всего 10%, может 15% - их мало. Ну мало у кого на проекте Apache Cassandra. Тем не менее, Spring Boot был задизайнен таким образом, что подключая условный spring-boot-starter-data-jdbc, вы всегда тянули к себе (чаще всего транзитивно, через spring-boot-starter) spring-boot-autoconfigure. То есть вы тянули автоконфигурации для вообще всего, что поддерживает Spring Boot.

Дизайн был простой - притянем автоконфигурации для всего, что можно (сами зависимости - опциональные!), а дальше уже там в рантайме поймем что из этого пользователю реально нужно.

Опять же, конечно, благодаря различного рода @ConditionalOnClass, SimpleAnnotationMetadata и непосредственно ASM и его Visitor-ам, Spring Boot имел возможность, не подгружая классы автоконфигурации, понимать, что условной Apache Cassandra у вас в проекте нет и соответственно автоконфигурация для неё подниматься не должна и класс автоконфигурации загружен в принципе не будет.

Но сам факт того, что у Вас есть в Class Path есть огромное количество автоконфигураций может быть немного опасным. И вот почему.

Проблема. Встречайте Антошку

Антошка парень хороший, традиционный Middle Java Developer. Он хороший как минимум потому, что написал тесты на ту фичу, что его попросили сделать. Он даже проверил локально - запустил сервис, отправил HTTP запрос, всё продебажил, всё работает.

Антошка даже метит в Senior-ы, и он знает, что иногда тяжелые запросы к БД можно закэшировать используя, например, Spring CacheManager. Для тестов подойдёт даже простой NoOpCacheManager, но на production-е, само собой, там прямо полноценный Redis.

И знаете, Spring Boot - крутая штука! Антошке даже не понадобилось в тестах определять никакой CacheManager - Spring Boot любезно притащил NoOpCacheConfiguration. Антошка об этом и не знал, но тесты же проходили, значит, всё ок!

Потом наш разработчик своё приложение задеплоил на окружение. И что-то странное там происходит... Антошка, будучи опытным разработчиком, начинает исследовать проблему. Выясняется, что на окружении, внезапно, поднялась RedisCacheConfiguration, и вместе с ней поднялись какие-то другие бины, которые поломали приложение, заинжектились куда попало и всё поехало.

Так вот, друзья, корень проблемы вот в чём:

Состояние окружения на build agent-е != Состояние production-а != Локальный workstation разработчика

И поэтому, из-за того, что spring-boot-autoconfigure является одним большим модулем, который тащит за собой всё, в рантайме на разных окружениях имеют свойство активироваться разные автоконфигурации. Это влияет на то, что покрыв код тестами и проверив локально, у вас всё равно отсутствует понимание того, а как же себя будет вести код на production, где, гипотетически:

  • Другой Cache

  • Где deployment на bare metal, а не в Kubernetes

  • Где конфигурация берётся из Х, а не из Spring Cloud Config и т.д.

В целом, гарантия, что приложение заработает на окружении при условии того, что приложение тестировали, отсутствует и без Spring Boot, но Spring Boot добавляет в данном случае свой вклад в общий котёл.

И что сделал Spring Boot 4?

Spring Boot 4 просто разбил spring-boot-autoconfigure на модули. Теперь каждый Spring Boot Starter подключает не spring-boot-autoconfigure, и именно нужный ему модуль автоконфигурации. Вот, например, что включает в себя spring-boot-starter-data-jdbc в Spring Boot 4 - никакого spring-boot-autoconfigure, только spring-boot-data-jdbc и только нужная автоконифигурация.

Опять же, друзья, я не хочу сказать, что модули на 100% искоренят проблему выше - я думаю, что в полной мере этого не произойдёт. Но как минимум теперь разработчик будет вынужден думать над тем, какие именно автоконфигурации ему нужно притащить в проект, и я считаю, что это в целом идея хорошая.

Ты точно хочешь иметь автоконфигурацию для Redis? Скажи об этом, пожалуйста, явно. Сами мы тебе её теперь не притащим. Вот такой подход.

Всем спасибо, что читали!

Комментарии (3)


  1. aleksandy
    21.11.2025 22:22

    Для тестов подойдёт даже простой NoOpCacheManager

    Нет, не подойдёт. Нахрена нужен тест, который тестирует совершенно не то окружение, в котором предполагается работа?


    1. SteveGrKek
      21.11.2025 22:22

      Есть юнит тесты. Даже для интеграционных не обязательно поднимаются все интеграции.


      1. aleksandy
        21.11.2025 22:22

        Есть юнит тесты

        Согласен. Но в данном случае, кэш-менеджер - это осознанно добавленная зависимость. Соответственно, она должна быть протестирована. Возможно, не эти тесты (с поднятием всех нужных зависимостей) могут быть неполными и покрывать только happycase, но они должны быть. Иначе можно вообще все интеграции NoOp-моками заменить. Просто чтобы тесты позеленить.