Spring Boot 应用启动组件加载顺序与优先级详解

这是一个非常核心且重要的问题!理解 Spring Boot 应用启动过程中组件的加载顺序和优先级,对于开发自定义 Starter、解决 Bean 依赖冲突、调试启动失败、优化启动性能都至关重要。

下面我将为你系统、详细、结构化 地讲解 Spring Boot 启动过程中的组件加载顺序与优先级机制,并配以中文注释示例和实战建议


🚀 Spring Boot 应用启动组件加载顺序与优先级详解


📌 一、整体启动流程概览(简化版)

Spring Boot 启动大致经历以下阶段:

复制代码
1. SpringApplication 实例化
2. 加载监听器(ApplicationListener)
3. 环境准备(EnvironmentPreparedEvent)
4. ApplicationContext 创建
5. BeanDefinition 加载(@ComponentScan, @Import, AutoConfiguration)
6. Bean 实例化(依赖注入、初始化)
7. ApplicationRunner / CommandLineRunner 执行
8. 应用启动完成(ApplicationReadyEvent)

我们重点关注 第5~6步:Bean 的加载与实例化顺序


📌 二、Bean 加载的核心顺序机制

Spring Boot 中 Bean 的加载顺序由以下机制共同决定(优先级从高到低):


✅ 1. @DependsOn ------ 强制依赖顺序(最高优先级)

🧩 作用:

强制指定当前 Bean 必须在另一个(或多个)Bean 初始化之后再初始化,无视其他顺序规则。

📝 示例:
java 复制代码
@Component
public class DatabaseInitializer {
    public DatabaseInitializer() {
        System.out.println("✅ DatabaseInitializer 初始化");
    }
}

@Component
@DependsOn("databaseInitializer") // 强制要求先初始化 databaseInitializer
public class CacheWarmUpService {
    public CacheWarmUpService() {
        System.out.println("✅ CacheWarmUpService 初始化(必须在 DatabaseInitializer 之后)");
    }
}

💡 输出顺序一定为:

复制代码
✅ DatabaseInitializer 初始化
✅ CacheWarmUpService 初始化
⚠️ 注意:
  • @DependsOn 是"硬性规定",即使被依赖的 Bean 是懒加载或条件加载,也会被强制提前初始化。
  • 支持多个依赖:@DependsOn({"a", "b"})

✅ 2. @Order / Ordered 接口 ------ 定义执行顺序(常用于监听器、拦截器、过滤器)

🧩 作用:

定义组件的逻辑执行顺序,值越小优先级越高。常用于:

  • ApplicationListener
  • WebMvcConfigurer
  • Filter
  • HandlerInterceptor
  • CommandLineRunner
📝 示例:
java 复制代码
@Component
@Order(1) // 优先级最高
public class FirstRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("🏃 第一个执行的 Runner");
    }
}

@Component
@Order(2)
public class SecondRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("🏃 第二个执行的 Runner");
    }
}

💡 输出:

复制代码
🏃 第一个执行的 Runner
🏃 第二个执行的 Runner
⚠️ 注意:
  • @Order 不影响 Bean 的创建顺序,只影响"执行顺序"。
  • 默认值 Ordered.LOWEST_PRECEDENCE(Integer.MAX_VALUE),即最低优先级。

✅ 3. @AutoConfigureBefore / @AutoConfigureAfter ------ 控制自动配置类加载顺序

🧩 作用:

控制自动配置类(@Configuration)之间的加载顺序,确保某个配置在另一个之前/之后被处理。

📝 示例:
java 复制代码
@Configuration
@AutoConfigureBefore(DataSourceAutoConfiguration.class) // 在数据源配置前加载
public class CustomDataSourcePreConfig {

    @Bean
    public CustomPropertySource customPropertySource() {
        System.out.println("🔧 在 DataSourceAutoConfiguration 之前加载");
        return new CustomPropertySource();
    }
}
⚠️ 注意:
  • 仅对 @Configuration 类有效。
  • 用于解决自动配置之间的依赖关系(如:你的 Starter 依赖 DataSource,必须在其后加载)。

✅ 4. @Conditional* 系列注解 ------ 条件化加载(影响"是否加载",间接影响顺序)

🧩 作用:

根据条件决定是否加载某个 Bean 或配置类。虽然不直接控制顺序,但未满足条件的组件不会被加载,自然也不会参与顺序竞争。

📝 示例:
java 复制代码
@Configuration
@ConditionalOnProperty(name = "my.feature.enabled", havingValue = "true")
public class ConditionalConfig {

    @Bean
    public MyService myService() {
        return new MyService(); // 仅当配置开启时才创建
    }
}

如果 my.feature.enabled=false,则 MyService 根本不会被创建,自然也不会参与初始化顺序。


✅ 5. 构造器注入 / @Autowired ------ 依赖驱动顺序(Spring 默认机制)

🧩 作用:

Spring 默认按依赖关系 决定初始化顺序:被依赖的 Bean 会先初始化

📝 示例:
java 复制代码
@Service
public class UserService {
    public UserService() {
        System.out.println("👤 UserService 初始化");
    }
}

@Service
public class OrderService {

    private final UserService userService;

    public OrderService(UserService userService) { // 依赖 UserService
        this.userService = userService;
        System.out.println("🛒 OrderService 初始化");
    }
}

💡 输出:

复制代码
👤 UserService 初始化
🛒 OrderService 初始化
⚠️ 注意:
  • 这是 Spring 最自然的顺序机制。
  • 避免循环依赖(A 依赖 B,B 依赖 A),否则启动失败。

✅ 6. @Lazy ------ 延迟初始化(最后加载)

🧩 作用:

标记 Bean 为懒加载,只有在第一次被注入或调用时才初始化,启动时不加载。

📝 示例:
java 复制代码
@Service
@Lazy // 启动时不初始化
public class ExpensiveService {
    public ExpensiveService() {
        System.out.println("💸 昂贵服务初始化(延迟)");
    }
}

@Service
public class NormalService {
    private final ExpensiveService expensiveService;

    public NormalService(ExpensiveService expensiveService) {
        this.expensiveService = expensiveService;
        System.out.println("✅ NormalService 初始化");
    }

    public void useExpensive() {
        // 第一次调用时才会触发 ExpensiveService 初始化
        expensiveService.doSomething();
    }
}

💡 启动时输出:

复制代码
✅ NormalService 初始化

调用 useExpensive() 时输出:

复制代码
💸 昂贵服务初始化(延迟)

📌 三、实战:综合优先级排序表

机制 作用范围 优先级 说明
@DependsOn Bean 实例化 ⭐⭐⭐⭐⭐ 最高 强制指定依赖顺序,无视其他规则
构造器注入 / @Autowired Bean 实例化 ⭐⭐⭐⭐ Spring 默认依赖驱动顺序
@Conditional* Bean 加载决策 ⭐⭐⭐ 决定"是否加载",间接影响顺序
@AutoConfigureBefore/After 自动配置类 ⭐⭐ 控制配置类加载顺序
@Order / Ordered 执行顺序 不影响创建顺序,只影响执行顺序
@Lazy Bean 实例化 最低 延迟到首次使用时才初始化

一句话总结优先级:
@DependsOn > 依赖注入 > 条件装配 > 自动配置顺序 > @Order > @Lazy


📌 四、查看实际加载顺序的方法

方法1:启用调试日志

application.yaml 中添加:

yaml 复制代码
logging:
  level:
    org.springframework: DEBUG
    org.springframework.boot.autoconfigure: TRACE

启动时会输出详细 Bean 加载顺序:

复制代码
Creating shared instance of singleton bean 'userService'
Creating shared instance of singleton bean 'orderService'

方法2:使用 BeanFactoryPostProcessor 打印顺序

java 复制代码
@Component
public class BeanInitializationOrderPrinter implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String[] beanNames = beanFactory.getBeanDefinitionNames();
        System.out.println("=== BeanDefinition 注册顺序 ===");
        for (int i = 0; i < beanNames.length; i++) {
            System.out.println((i + 1) + ". " + beanNames[i]);
        }
    }
}

⚠️ 注意:这只是注册顺序,不是初始化顺序!


方法3:使用 SmartInitializingSingleton 查看初始化完成顺序

java 复制代码
@Component
public class InitializationOrderLogger implements SmartInitializingSingleton {

    @Override
    public void afterSingletonsInstantiated() {
        System.out.println("✅ 所有单例 Bean 初始化完成");
    }
}

📌 五、实战建议与最佳实践

✅ 1. 优先使用自然依赖注入顺序

  • 让 Spring 自动管理依赖顺序是最安全、最可维护的方式。
  • 避免过度使用 @DependsOn,容易造成隐式耦合。

✅ 2. 自定义 Starter 必须使用 @AutoConfigureAfter

  • 如果你的 Starter 依赖 DataSourceRedisTemplate 等,务必在其后加载:
java 复制代码
@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyStarterAutoConfiguration { ... }

✅ 3. 初始化任务使用 ApplicationRunner + @Order

  • 不要用 @PostConstruct 做复杂初始化,改用 ApplicationRunner
java 复制代码
@Component
@Order(1)
public class DataInitRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 此时所有 Bean 已初始化完毕,安全执行业务逻辑
    }
}

✅ 4. 避免循环依赖

  • 使用 @Lazy 或重构设计解决循环依赖。
  • 启动报错:The dependencies of some of the beans... form a cycle

✅ 5. 敏感资源初始化使用 @DependsOn

  • 如:缓存预热必须在数据库初始化之后:
java 复制代码
@Service
@DependsOn("databaseMigrationService")
public class CachePreloadService { ... }

📌 六、完整示例:模拟真实项目启动顺序

java 复制代码
// 1. 数据库迁移(必须最先执行)
@Component
public class DatabaseMigrationService {
    public DatabaseMigrationService() {
        System.out.println("1️⃣ DatabaseMigrationService 初始化");
    }
}

// 2. 缓存预热(必须在数据库之后)
@Component
@DependsOn("databaseMigrationService")
public class CacheWarmUpService {
    public CacheWarmUpService() {
        System.out.println("2️⃣ CacheWarmUpService 初始化");
    }
}

// 3. 用户服务(自然依赖)
@Service
public class UserService {
    public UserService() {
        System.out.println("3️⃣ UserService 初始化");
    }
}

// 4. 订单服务(依赖用户服务)
@Service
public class OrderService {
    public OrderService(UserService userService) {
        System.out.println("4️⃣ OrderService 初始化");
    }
}

// 5. 启动后执行任务
@Component
@Order(1)
public class StartupTaskRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) {
        System.out.println("5️⃣ StartupTaskRunner 执行");
    }
}

// 6. 懒加载服务
@Service
@Lazy
public class ReportService {
    public ReportService() {
        System.out.println("6️⃣ ReportService 初始化(延迟)");
    }
}

💡 启动输出:

复制代码
1️⃣ DatabaseMigrationService 初始化
2️⃣ CacheWarmUpService 初始化
3️⃣ UserService 初始化
4️⃣ OrderService 初始化
5️⃣ StartupTaskRunner 执行

ReportService 只有在首次调用时才会输出。


✅ 总结

掌握 Spring Boot 组件加载顺序,你就能:

  • ✅ 精准控制 Bean 初始化时机
  • ✅ 避免启动失败和循环依赖
  • ✅ 优化启动性能(延迟加载)
  • ✅ 开发健壮的自定义 Starter
  • ✅ 调试复杂启动问题

记住这个口诀:

"依赖决定顺序,条件决定有无,DependsOn 是老大,Lazy 最后才出场"


如需我为你分析某个具体场景的加载顺序(如整合 Redis、RabbitMQ、Security 等),欢迎继续提问!

祝你成为 Spring Boot 启动大师!🚀

相关推荐
GetcharZp23 分钟前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑1 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯2 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
fanly113 小时前
Surging AI Agent 完整产品介绍
微服务·microservice
lizhongxuan4 小时前
多Agent之间的区别
后端
青石路6 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充6 小时前
1.面向对象设计思想
后端
IT_陈寒6 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro7 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗7 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端