本文系统梳理 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在此判断是否需要提前创建代理 AutowiredAnnotationBeanPostProcessor在postProcessProperties中完成@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 接口 | 注入内容 | 典型场景 | 版本 |
|---|---|---|---|
BeanNameAware | Bean 名称 | 日志打印、分布式锁 key | 1.0+ |
BeanClassLoaderAware | ClassLoader | 动态加载类 | 2.0+ |
BeanFactoryAware | BeanFactory | 编程式获取 Bean | 1.0+ |
EnvironmentAware | Environment | 读取 Profile、配置 | 3.1+ |
ResourceLoaderAware | ResourceLoader | 加载 classpath 资源 | 2.0+ |
ApplicationEventPublisherAware | 事件发布器 | 在非 Spring Bean 中发布事件 | 1.1.1+ |
ApplicationContextAware | ApplicationContext | 实现 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 初始化方法执行前后(postProcessBeforeInitialization 和 postProcessAfterInitialization)。
核心能力:对容器中所有 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
对比
| 扩展点 | 标准 | 侵入性 | 适用场景 |
|---|---|---|---|
@PostConstruct | JSR-250 | 低(注解) | 业务代码初始化:缓存预热、参数校验、策略注册 |
InitializingBean | Spring 原生 | 中(实现接口) | 框架/中间件内部:RedisTemplate、JdbcTemplate |
init-method | Spring 原生 | 无(配置化) | 第三方类初始化,无法修改源码 |
验证示例
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[] args,ApplicationRunner 接收解析后的 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.x | InitializingBean、DisposableBean、BeanFactoryAware、BeanNameAware |
| 2.0 | BeanPostProcessor、BeanFactoryPostProcessor、Lifecycle、InstantiationAwareBeanPostProcessor |
| 2.5 | @PostConstruct、@PreDestroy(引入 JSR-250 支持) |
| 3.0 | BeanDefinitionRegistryPostProcessor、SmartLifecycle |
| 3.1 | EnvironmentAware、@Profile |
| 4.1 | SmartInitializingSingleton |
| 5.0 | 函数式 Bean 注册(GenericApplicationContext#registerBean) |
| 6.0 / Boot 3.x | jakarta.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 的启动扩展点设计体现了开闭原则——框架核心流程固定,但在每个关键节点都留出了扩展空间。理解这些扩展点的执行顺序和适用场景,能帮助你:
- 在正确的时机做正确的事——避免初始化顺序导致的 NPE 或数据不一致
- 以最小侵入性实现需求——优先选择注解,其次接口,最后配置
- 写出框架级别的代码——理解 MyBatis、Dubbo、Spring AOP 等框架的底层实现原理
评论区