← Назад к вопросам

Как Spring Boot запускает Bean в правильном порядке

2.4 Senior🔥 101 комментариев
#Spring Boot и Spring Data

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как Spring Boot запускает Bean в правильном порядке

Основная идея

Spring применяет граф зависимостей (dependency graph) для определения порядка инициализации Bean. Если Bean A зависит от Bean B, то B инициализируется ДО A. Spring автоматически анализирует конструкторы, поля и сеттеры для определения зависимостей.

Простой пример

@Component
public class DatabaseConnection {
    public DatabaseConnection() {
        System.out.println("DatabaseConnection создан");
    }
}

@Component
public class UserRepository {
    private DatabaseConnection db;
    
    public UserRepository(DatabaseConnection db) {
        System.out.println("UserRepository создан");
        this.db = db;
    }
}

@Component
public class UserService {
    private UserRepository repo;
    
    public UserService(UserRepository repo) {
        System.out.println("UserService создан");
        this.repo = repo;
    }
}

// При запуске Spring Boot:
// DatabaseConnection создан       <- 1-й
// UserRepository создан           <- 2-й (зависит от DatabaseConnection)
// UserService создан              <- 3-й (зависит от UserRepository)

Граф зависимостей

UserService
    ↓ зависит от
UserRepository
    ↓ зависит от
DatabaseConnection

Порядок инициализации (от листьев к корню):
1. DatabaseConnection (нет зависимостей)
2. UserRepository (зависит от DatabaseConnection)
3. UserService (зависит от UserRepository)

Алгоритм Spring для определения порядка

public class BeanDependencyResolver {
    /*
    1. Spring сканирует все классы с @Component, @Service, @Repository и т.д.
    
    2. Для каждого Bean анализирует зависимости через:
       - Конструктор (Constructor Injection) <- ПРЕДПОЧТИТЕЛЬНО
       - Поля (@Autowired Field Injection)
       - Сеттеры (Setter Injection)
    
    3. Строит граф зависимостей (Directed Acyclic Graph, DAG)
    
    4. Топологически сортирует (Topological Sort)
       └─ Чтобы зависимости инициализировались перед зависимыми
    
    5. Инициализирует в полученном порядке
    */
}

Пример: анализ зависимостей

@Configuration
public class AppConfig {
    
    @Bean
    public DatabaseConnection databaseConnection() {
        System.out.println("[1] DatabaseConnection создан");
        return new DatabaseConnection();
    }
    
    @Bean
    public UserRepository userRepository(DatabaseConnection db) {
        System.out.println("[2] UserRepository создан");
        return new UserRepository(db);
    }
    
    @Bean
    public UserService userService(UserRepository repo) {
        System.out.println("[3] UserService создан");
        return new UserService(repo);
    }
    
    @Bean
    public UserController userController(UserService service) {
        System.out.println("[4] UserController создан");
        return new UserController(service);
    }
}

// Spring анализирует сигнатуры методов:
// databaseConnection() -> зависит от: ничего
// userRepository(DatabaseConnection db) -> зависит от: DatabaseConnection
// userService(UserRepository repo) -> зависит от: UserRepository
// userController(UserService service) -> зависит от: UserService

// Порядок инициализации:
// [1] DatabaseConnection
// [2] UserRepository (параметр: DatabaseConnection)
// [3] UserService (параметр: UserRepository)
// [4] UserController (параметр: UserService)

Циклические зависимости (проблема)

// ❌ ОШИБКА: циклическая зависимость
@Component
public class ServiceA {
    @Autowired
    private ServiceB serviceB;  // ServiceA -> ServiceB
}

@Component
public class ServiceB {
    @Autowired
    private ServiceA serviceA;  // ServiceB -> ServiceA
    
    // ServiceA -> ServiceB -> ServiceA -> ... бесконечный цикл!
}

// Spring выбросит ошибку:
// BeanCurrentlyInCreationException: 
// Bean with name 'serviceA' has been circularly referenced

// Решение 1: @Lazy
@Component
public class ServiceB {
    @Autowired
    @Lazy  // Отложенная инициализация
    private ServiceA serviceA;  // ServiceA инициализируется позже
}

// Решение 2: Setter Injection вместо Constructor
@Component
public class ServiceB {
    private ServiceA serviceA;
    
    @Autowired
    public void setServiceA(ServiceA serviceA) {  // Сеттер
        this.serviceA = serviceA;
    }
}

Порядок инициализации: подробно

public class SpringBootInitializationOrder {
    /*
    1. Spring ApplicationContext создаёт граф Bean'ов
       └─ Сканирует @ComponentScan пакеты
       └─ Загружает @Bean методы из @Configuration классов
    
    2. Анализирует зависимости
       └─ Конструктор имеет приоритет (required dependency)
       └─ Поля (@Autowired) анализируются
       └─ Сеттеры анализируются
    
    3. Проверяет циклические зависимости
       └─ Если есть -> BeanCurrentlyInCreationException
    
    4. Топологическая сортировка
       └─ DFS (Depth-First Search) или BFS (Breadth-First Search)
       └─ Результат: список Bean'ов в порядке инициализации
    
    5. Инициализирует Bean'ы по очереди
       
       Для каждого Bean:
       a) Вызов конструктора (Constructor Injection)
       b) Установка полей (@Autowired Field Injection)
       c) Вызов сеттеров (Setter Injection)
       d) @PostConstruct методы
       e) InitializingBean.afterPropertiesSet()
       f) Bean готов к использованию
    */
}

Жизненный цикл Bean

@Component
public class MyBean implements InitializingBean, DisposableBean {
    
    // ✅ 1. Конструктор (создание объекта)
    public MyBean() {
        System.out.println("1. Конструктор вызван");
    }
    
    // ✅ 2. Dependency Injection (внедрение зависимостей)
    @Autowired
    private AnotherBean anotherBean;  // Внедряется сюда
    
    // ✅ 3. Инициализация
    @PostConstruct
    public void init() {
        System.out.println("3. @PostConstruct");
        // Здесь можно открыть соединение, загрузить кеш и т.д.
    }
    
    // Альтернатива @PostConstruct:
    @Override
    public void afterPropertiesSet() {
        System.out.println("3b. afterPropertiesSet (InitializingBean)");
    }
    
    // ✅ 4. Bean готов к использованию
    public void doSomething() {
        System.out.println("4. Bean используется");
    }
    
    // ✅ 5. Завершение (шатдаун)
    @PreDestroy
    public void cleanup() {
        System.out.println("5. @PreDestroy");
        // Здесь закрыть соединение, сохранить состояние и т.д.
    }
    
    @Override
    public void destroy() {
        System.out.println("5b. destroy (DisposableBean)");
    }
}

// Вывод при запуске Spring Boot:
// 1. Конструктор вызван
// 3. @PostConstruct
// 4. Bean используется
// 5. @PreDestroy (при выключении приложения)

Порядок внедрения зависимостей

@Component
public class MyService {
    
    // Способ 1: Constructor Injection (ЛУЧШИЙ)
    // Spring ищет параметр в конструкторе и внедряет его
    private AnotherBean anotherBean;
    
    public MyService(AnotherBean anotherBean) {
        System.out.println("1. Constructor Injection");
        this.anotherBean = anotherBean;
    }
    
    // Способ 2: Field Injection (не рекомендуется)
    // @Autowired
    // private AnotherBean anotherBean;
    // Spring использует рефлексию для установки поля
    // Минус: трудно тестировать, зависимость скрыта
    
    // Способ 3: Setter Injection
    // private AnotherBean anotherBean;
    // @Autowired
    // public void setAnotherBean(AnotherBean anotherBean) {
    //     this.anotherBean = anotherBean;
    // }
}

// Порядок выбора Spring:
// 1. Constructor Injection (если есть конструктор с параметрами)
// 2. Field Injection (@Autowired на поля)
// 3. Setter Injection (@Autowired на сеттеры)

Пример с условными Bean'ами

@Configuration
public class ConditionalBeanConfig {
    
    @Bean
    @ConditionalOnProperty(name = "app.db.type", havingValue = "postgres")
    public DatabaseConnection postgresConnection() {
        System.out.println("Postgres Bean создан");
        return new PostgresConnection();
    }
    
    @Bean
    @ConditionalOnProperty(name = "app.db.type", havingValue = "mysql")
    public DatabaseConnection mysqlConnection() {
        System.out.println("MySQL Bean создан");
        return new MySQLConnection();
    }
    
    @Bean
    public UserRepository userRepository(DatabaseConnection db) {
        System.out.println("UserRepository создан");
        return new UserRepository(db);
    }
}

// Если app.db.type=postgres:
// Postgres Bean создан <- DatabaseConnection
// UserRepository создан <- зависит от DatabaseConnection

// Если app.db.type=mysql:
// MySQL Bean создан <- DatabaseConnection
// UserRepository создан <- зависит от DatabaseConnection

Можно ли контролировать порядок явно?

@Configuration
public class ExplicitOrderConfig {
    
    @Bean
    public ServiceA serviceA() {
        return new ServiceA();
    }
    
    @Bean
    public ServiceB serviceB(ServiceA serviceA) {  // Явная зависимость
        return new ServiceB(serviceA);
    }
    
    // Альтернатива: @Lazy и ObjectProvider
    @Bean
    public ServiceC serviceC(ObjectProvider<ServiceB> serviceBProvider) {
        // serviceBProvider.getIfAvailable() вернёт ServiceB или null
        // Даёт контроль над моментом инициализации
        return new ServiceC(serviceBProvider.getIfAvailable());
    }
}

// @Depends аннотация (редко)
@Bean(name = "serviceD", dependsOn = {"serviceA", "serviceB"})
public ServiceD serviceD() {
    return new ServiceD();
    // Spring гарантирует, что serviceA и serviceB инициализируются первыми
}

Итого: правильный порядок инициализации

  1. Spring анализирует зависимости через конструкторы, поля и сеттеры
  2. Строит граф зависимостей (DAG — Directed Acyclic Graph)
  3. Топологически сортирует Bean'ы (зависимости перед зависимыми)
  4. Инициализирует по порядку:
    • Конструктор → Field Injection → Setter Injection → @PostConstruct
  5. Обнаруживает циклические зависимости и выбрасывает ошибку

Порядок в 99% случаев правильный, если вы используете Constructor Injection (это лучшая практика).