侧边栏壁纸
博主头像
牧云

怀璧慎显,博识谨言。

  • 累计撰写 205 篇文章
  • 累计创建 20 个标签
  • 累计收到 8 条评论

目 录CONTENT

文章目录

Spring 启动扩展点全解析

秋之牧云
2026-06-11 / 0 评论 / 0 点赞 / 8 阅读 / 0 字

本文系统梳理 Spring/Spring Boot 启动过程中的所有扩展点,按执行顺序逐一讲解其原理、使用场景和代码示例,并附上版本演进对照表和选型决策树,帮助你在实际项目中精准选择合适的扩展点。

一、为什么需要了解启动扩展点?

Spring 容器的启动过程并非一个黑盒——它在 Bean 的定义、实例化、初始化、就绪等各个阶段都预留了钩子(Hook)。理解这些扩展点,意味着你能够:

  • 精确控制初始化时机:缓存预热、连接池初始化、服务注册等操作放在正确的阶段执行
  • 优雅地介入框架行为:动态注册 Bean、修改 Bean 定义、生成 AOP 代理
  • 避免踩坑:比如在 Bean 还没完成注入时就使用依赖,或在容器未就绪时就注册到注册中心

本文将按照执行顺序,逐一剖析每个扩展点。


二、完整执行顺序总览

先给出全局视角,后面逐一展开:

BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
  ↓
BeanFactoryPostProcessor#postProcessBeanFactory
  ↓
InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
  ↓
  构造器(Bean 实例化)
  ↓
MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
  ↓
InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
  ↓
  属性注入(@Autowired / @Value / @Resource)
  ↓
  Aware 接口回调(BeanNameAware → BeanFactoryAware → ApplicationContextAware ...)
  ↓
BeanPostProcessor#postProcessBeforeInitialization
  ↓
  @PostConstruct
  ↓
  InitializingBean#afterPropertiesSet
  ↓
  自定义 init-method
  ↓
BeanPostProcessor#postProcessAfterInitialization
  ↓
SmartInitializingSingleton#afterSingletonsInstantiated
  ↓
SmartLifecycle#start
  ↓
ContextRefreshedEvent
  ↓
CommandLineRunner / ApplicationRunner(Spring Boot)
  ↓
ApplicationReadyEvent(Spring Boot)

三、逐一详解

1. BeanDefinitionRegistryPostProcessor

执行时机:所有 Bean 定义加载完成后,最早执行的扩展点。

核心能力:向容器中动态注册新的 BeanDefinition

典型场景

  • MyBatis 的 MapperScannerConfigurer 扫描 @Mapper 接口,为每个接口动态注册代理 Bean
  • 自定义 RPC 框架扫描 @RpcReference 注解,动态注册远程代理
  • 根据配置文件动态注册多个数据源

代码示例

@Component
public class CustomBeanRegistrar implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        GenericBeanDefinition definition = new GenericBeanDefinition();
        definition.setBeanClass(MyDynamicService.class);
        registry.registerBeanDefinition("myDynamicService", definition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }
}

版本:Spring 3.0+


2. BeanFactoryPostProcessor

执行时机:在 BeanDefinitionRegistryPostProcessor 之后,所有 Bean 实例化之前。

核心能力修改已有的 BeanDefinition,但不能注册新 Bean。

典型场景

  • PropertySourcesPlaceholderConfigurer${db.url} 占位符替换为实际值
  • 加密配置解密:将 ENC(xxx) 格式的密码在 Bean 实例化前解密
  • 根据环境变量修改 Bean 的 scope、lazy-init 等属性

代码示例

@Component
public class EncryptedPropertyProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition bd = beanFactory.getBeanDefinition("dataSource");
        MutablePropertyValues pvs = bd.getPropertyValues();
        PropertyValue pv = pvs.getPropertyValue("password");
        if (pv != null && pv.getValue().toString().startsWith("ENC(")) {
            String decrypted = decrypt(pv.getValue().toString());
            pvs.addPropertyValue("password", decrypted);
        }
    }
}

版本:Spring 2.0+


3. InstantiationAwareBeanPostProcessor

执行时机:Bean 实例化前后。

核心能力:拦截 Bean 的实例化过程,可以返回代理对象替代原始 Bean;也可以拦截属性注入过程。

典型场景

  • Spring AOP 的 AbstractAutoProxyCreator 在此判断是否需要提前创建代理
  • AutowiredAnnotationBeanPostProcessorpostProcessProperties 中完成 @Autowired 注入
  • 三级缓存中的 early reference 通过此扩展点暴露,解决循环依赖

代码示例

@Component
public class CustomInstantiationProcessor implements InstantiationAwareBeanPostProcessor {

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (beanClass == OrderService.class) {
            System.out.println("OrderService 即将实例化");
        }
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return true;
    }
}

版本:Spring 2.0+


4. Aware 接口家族

执行时机:属性注入完成后,初始化方法执行前。

核心能力:让 Bean 获取 Spring 基础设施对象的引用。

Aware 接口注入内容典型场景版本
BeanNameAwareBean 名称日志打印、分布式锁 key1.0+
BeanClassLoaderAwareClassLoader动态加载类2.0+
BeanFactoryAwareBeanFactory编程式获取 Bean1.0+
EnvironmentAwareEnvironment读取 Profile、配置3.1+
ResourceLoaderAwareResourceLoader加载 classpath 资源2.0+
ApplicationEventPublisherAware事件发布器在非 Spring Bean 中发布事件1.1.1+
ApplicationContextAwareApplicationContext实现 SpringContextUtil.getBean() 工具类1.0+

代码示例

@Component
public class SpringContextUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        SpringContextUtil.context = ctx;
    }

    public static <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }
}

5. BeanPostProcessor

执行时机:Bean 初始化方法执行前后(postProcessBeforeInitializationpostProcessAfterInitialization)。

核心能力:对容器中所有 Bean 做统一增强。这是 Spring 最核心的扩展点之一。

典型场景

  • AOP 代理生成(AbstractAutoProxyCreator
  • @Scheduled 定时任务注册
  • @Async 异步方法代理
  • 自定义注解处理:@RateLimiter@Log@Retry

代码示例——自动为带 @Log 注解的方法生成日志代理:

@Component
public class LogAnnotationProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> clazz = bean.getClass();
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Log.class)) {
                return createLogProxy(bean);
            }
        }
        return bean;
    }

    private Object createLogProxy(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            (proxy, method, args) -> {
                long start = System.currentTimeMillis();
                Object result = method.invoke(target, args);
                System.out.println(method.getName() + " 耗时: " + (System.currentTimeMillis() - start) + "ms");
                return result;
            }
        );
    }
}

版本:Spring 2.0+


6. @PostConstruct、InitializingBean、init-method

这三者功能相似,都是在 Bean 依赖注入完成后执行初始化逻辑,但执行顺序和适用场景不同

执行顺序

@PostConstruct → InitializingBean#afterPropertiesSet → init-method

对比

扩展点标准侵入性适用场景
@PostConstructJSR-250低(注解)业务代码初始化:缓存预热、参数校验、策略注册
InitializingBeanSpring 原生中(实现接口)框架/中间件内部:RedisTemplateJdbcTemplate
init-methodSpring 原生无(配置化)第三方类初始化,无法修改源码

验证示例

public class LifecycleDemo implements InitializingBean {

    @PostConstruct
    public void postConstruct() {
        System.out.println("1️⃣ @PostConstruct");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("2️⃣ InitializingBean#afterPropertiesSet");
    }

    public void customInit() {
        System.out.println("3️⃣ init-method");
    }
}

@Configuration
public class DemoConfig {
    @Bean(initMethod = "customInit")
    public LifecycleDemo lifecycleDemo() {
        return new LifecycleDemo();
    }
}

输出:

1️⃣ @PostConstruct
2️⃣ InitializingBean#afterPropertiesSet
3️⃣ init-method

最佳实践:业务代码优先用 @PostConstruct,框架代码用 InitializingBean,第三方类用 init-method


7. SmartInitializingSingleton

执行时机:所有非懒加载单例 Bean 初始化完成后(整个容器级别)。

核心能力:确保所有 Bean 都已就绪后再执行操作。

典型场景

  • 收集所有 Handler Bean,构建事件路由表
  • 一致性校验:检查策略是否覆盖了所有枚举值
  • 接口文档生成:所有 Controller 就绪后扫描 API

代码示例——策略完整性校验:

@Component
public class PayStrategyValidator implements SmartInitializingSingleton {

    @Autowired
    private Map<String, PayStrategy> strategyMap;

    @Override
    public void afterSingletonsInstantiated() {
        for (PayType type : PayType.values()) {
            boolean found = strategyMap.values().stream()
                    .anyMatch(s -> s.supportType() == type);
            if (!found) {
                throw new IllegalStateException("缺少支付策略: " + type);
            }
        }
    }
}

版本:Spring 4.1+


8. SmartLifecycle

执行时机:容器刷新完成后启动,容器关闭前停止。

核心能力:通过 getPhase() 精确控制多个组件的启动/停止顺序,支持异步优雅关闭。

典型场景

  • MQ 消费者启停:先启动数据库连接池,再启动消费者
  • gRPC/HTTP Server 管理
  • 优雅上下线:先摘流量再停服务

代码示例——优雅上下线:

@Component
public class GracefulShutdown implements SmartLifecycle {

    private volatile boolean running = false;

    @Override
    public int getPhase() {
        return Integer.MAX_VALUE;
    }

    @Override
    public void start() {
        running = true;
        registerToNacos();
    }

    @Override
    public void stop(Runnable callback) {
        deregisterFromNacos();
        waitForInFlightRequests(30, TimeUnit.SECONDS);
        running = false;
        callback.run();
    }

    @Override
    public boolean isRunning() {
        return running;
    }
}

版本:Spring 3.0+


9. 事件监听

ContextRefreshedEvent

容器刷新完成时触发。

@Component
public class StartupChecker implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        checkRedisConnection();
        checkDatabaseConnection();
        System.out.println("启动自检通过");
    }
}

⚠️ 在有父子容器的场景(如传统 Spring MVC),此事件会触发多次

ApplicationReadyEvent(Spring Boot)

应用完全就绪后触发,是最安全的"启动完成"信号。

@Component
public class ReadyNotifier {

    @EventListener(ApplicationReadyEvent.class)
    public void onReady() {
        dingTalkNotify("应用启动成功 ✅");
        reportToMonitor("service.started", 1);
    }
}

10. CommandLineRunner / ApplicationRunner(Spring Boot)

执行时机:Spring Boot 应用启动的最后阶段。

区别CommandLineRunner 接收原始 String[] argsApplicationRunner 接收解析后的 ApplicationArguments

典型场景

  • 数据迁移脚本
  • CLI 工具(执行后退出)
  • 打印启动摘要信息
@Component
@Order(1)
public class DataInitRunner implements CommandLineRunner {

    @Autowired
    private UserRepository userRepository;

    @Override
    public void run(String... args) throws Exception {
        if (userRepository.count() == 0) {
            userRepository.save(new User("admin", "admin@example.com"));
            System.out.println("初始化管理员账号完成");
        }
    }
}

四、版本演进对照表

Spring 版本新增扩展点
1.xInitializingBeanDisposableBeanBeanFactoryAwareBeanNameAware
2.0BeanPostProcessorBeanFactoryPostProcessorLifecycleInstantiationAwareBeanPostProcessor
2.5@PostConstruct@PreDestroy(引入 JSR-250 支持)
3.0BeanDefinitionRegistryPostProcessorSmartLifecycle
3.1EnvironmentAware@Profile
4.1SmartInitializingSingleton
5.0函数式 Bean 注册(GenericApplicationContext#registerBean
6.0 / Boot 3.xjakarta.annotation.PostConstruct 替代 javax.annotation.PostConstruct

五、选型决策树

面对具体需求时,按照以下路径选择:

需要修改或注册 Bean 定义?
├── 是 → 需要注册新 Bean?
│     ├── 是 → BeanDefinitionRegistryPostProcessor
│     └── 否 → BeanFactoryPostProcessor
└── 否 → 需要增强所有 Bean(AOP/注解处理)?
        ├── 是 → BeanPostProcessor
        └── 否 → 只需初始化当前 Bean?
                ├── 是 → 能修改源码?
                │     ├── 是 → @PostConstruct(业务)/ InitializingBean(框架)
                │     └── 否 → @Bean(initMethod = "xxx")
                └── 否 → 需要等所有 Bean 就绪?
                        ├── 是 → SmartInitializingSingleton
                        └── 否 → 需要控制启停顺序/优雅关闭?
                                ├── 是 → SmartLifecycle
                                └── 否 → CommandLineRunner / ApplicationReadyEvent

六、常见踩坑指南

1. @PostConstruct 中注入的 Bean 可能还没初始化完?

不会。@PostConstruct 执行时,当前 Bean 的所有依赖已经注入完成。但如果依赖的 Bean 内部有异步初始化逻辑,确实可能还没完全就绪。

2. ContextRefreshedEvent 被触发多次

在传统 Spring MVC(DispatcherServlet 有独立子容器)场景下,父容器和子容器各触发一次。解决方案:

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
    if (event.getApplicationContext().getParent() == null) {
        // 只在根容器触发时执行
    }
}

3. BeanPostProcessor 中不要注入普通 Bean

BeanPostProcessor 会在非常早期被实例化。如果它通过 @Autowired 注入其他 Bean,那些 Bean 会被提前初始化,跳过其他 BeanPostProcessor 的处理(如 AOP 代理不会生效)。

4. @PostConstruct 在 Spring 6 / Boot 3 中的包变更

javax.annotation.PostConstruct 变为 jakarta.annotation.PostConstruct,迁移时注意 import。


七、总结

Spring 的启动扩展点设计体现了开闭原则——框架核心流程固定,但在每个关键节点都留出了扩展空间。理解这些扩展点的执行顺序和适用场景,能帮助你:

  1. 在正确的时机做正确的事——避免初始化顺序导致的 NPE 或数据不一致
  2. 以最小侵入性实现需求——优先选择注解,其次接口,最后配置
  3. 写出框架级别的代码——理解 MyBatis、Dubbo、Spring AOP 等框架的底层实现原理
0

评论区