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 启动大师!🚀

相关推荐
青云交2 小时前
Java 大视界 -- Java 大数据在智能公交调度优化与准点率提升中的应用实践(416)
java·动态规划·flink cep·spark mllib·智能公交调度·杭州公交案例·准点率提升
RainbowSea2 小时前
4. ChatClient 的初始,快速使用上手
java·spring·ai编程
RainbowSea2 小时前
3. Ollama 安装,流式输出,多模态,思考模型
java·spring·ai编程
沧澜sincerely3 小时前
Redis 键空间 & 五大类型
java·redis·docker
235163 小时前
【LeetCode】3. 无重复字符的最长子串
java·后端·算法·leetcode·职场和发展
nandao1583 小时前
springBoot 集成Neo4j 实战演示
java·spring boot·neo4j
可观测性用观测云3 小时前
解锁DQL高级玩法——对日志关键信息提取和分析
后端
零雲3 小时前
java面试:可以讲一讲sychronized和ReentrantLock的异同点吗
java·开发语言·面试
没有bug.的程序员3 小时前
SQL 执行计划解析:从 EXPLAIN 到性能优化的完整指南
java·数据库·sql·性能优化·explain·执行计划