我总是记不住 Spring 的接入点,一个字,乱!

BeanFactoryPostProcessor、BeanPostProcessor、ApplicationListener、ApplicationContextInitializer、SmartInitializingSingleton、SmartLifecycle……名字都认识,但一到用的时候就乱:
到底谁先谁后?谁改配置?谁改 Bean?谁监听启动?谁负责关闭?

后来我接了一个多租户任务平台,叫 TaskPilot,需求一串:

  1. 启动前从配置中心拉租户配置;
  2. 每个租户动态注册一个数据源;
  3. 给任务处理器加审计代理;
  4. 所有任务处理器创建完后,校验任务类型不能重复;
  5. 应用启动完成后预热缓存;
  6. MQ 消费者要随容器启动,关闭时优雅停机。

做完这个项目,我突然发现:
Spring 接入点根本不用背,它们就是容器一生中的一个个插槽。

而且除了“插槽”,还有两条暗线贯穿始终:

  • Aware 线:Bean 在生命周期里认领容器资源;
  • 事件线:容器一生都在广播,谁想听就听。

下面跟着 TaskPilot 的生命周期走一遍。


先看总图:容器的一生

四条线一起看:外层是 Spring Boot 启动流程,内层是 Spring 容器 refresh() 流程,横线是事件广播,微线是 Bean 创建过程。

最重要的还是 Spring Boot 启动主流程,尤其是前 5 步。把这 5 步记住,后面的一系列问题都会自然而然地对应上。

1. Spring Boot 启动主流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
SpringApplication.run()
│
├─ 0. 发布 ApplicationStartingEvent
│
├─ 1. prepareEnvironment() // ★ 先于 ApplicationContext 创建
│ ├── 创建/获取 Environment
│ ├── 加载配置文件、命令行参数
│ ├── 触发 EnvironmentPostProcessor
│ └── 发布 ApplicationEnvironmentPreparedEvent
│
├─ 2. createApplicationContext()
│ └── 创建 ApplicationContext
│ └── 内部构造 DefaultListableBeanFactory // BeanFactory 在这里诞生
│
├─ 3. prepareContext()
│ ├── 设置 Environment
│ ├── 执行 ApplicationContextInitializer
│ ├── 发布 ApplicationContextInitializedEvent
│ ├── 注册主配置类 primarySources
│ ├── 注册监听器 ApplicationListener
│ └── 发布 ApplicationPreparedEvent
│
├─ 4. refreshContext() → refresh()
│
├─ 5. afterRefresh()
├─ 6. 发布 ApplicationStartedEvent
├─ 7. 调用 Runners(ApplicationRunner / CommandLineRunner)
├─ 8. 发布 ApplicationReadyEvent
└─ 9. run() 返回 → 应用运行

异常时:
└─ 发布 ApplicationFailedEvent

关闭时:
└─ 发布 ContextClosedEvent

2. refresh() 内部 12 步

1
2
3
4
5
6
7
8
9
10
11
12
13
refresh()
├─ prepareRefresh()
├─ obtainFreshBeanFactory()
├─ prepareBeanFactory()
├─ postProcessBeanFactory()
├─ invokeBeanFactoryPostProcessors() // 含 BeanDefinitionRegistryPostProcessor
├─ registerBeanPostProcessors()
├─ initMessageSource()
├─ initApplicationEventMulticaster() // 初始化事件多播器
├─ onRefresh() // 子类扩展,如内嵌容器启动
├─ registerListeners() // 注册 ApplicationListener
├─ finishBeanFactoryInitialization() // 实例化 + Aware + 初始化前后 + SmartInitializingSingleton
└─ finishRefresh() // SmartLifecycle.start + 发布 ContextRefreshedEvent

3. 事件时间线

1
2
3
4
5
6
7
8
9
10
11
ApplicationStartingEvent             ← Bean 不存在
ApplicationEnvironmentPreparedEvent ← Bean 不存在
ApplicationContextInitializedEvent ← Bean 不存在
ApplicationPreparedEvent ← Bean 不存在
────────────────────── refresh() ──────────────────────
ContextRefreshedEvent ← Bean 已存在
ApplicationStartedEvent ← Bean 已存在
ApplicationReadyEvent ← Bean 已存在
ApplicationFailedEvent ← 任意阶段抛异常
────────────────────── 关闭 ──────────────────────
ContextClosedEvent ← Bean 已存在

一句话记住:

改环境、初始化上下文、加图纸、改图纸、改成品、拿资源、做校验、听事件、管启停、做销毁。


第一幕:容器还没出生,先改环境

TaskPilot 启动前,需要从配置中心拉取租户列表。

这个时候 Spring 容器还没创建,你不可能拿到 ApplicationContext,更不可能拿 Bean。
你能改的只有 Environment。

接入点:EnvironmentPostProcessor。

1
2
3
4
5
6
7
8
9
10
11
public class TenantEnvironmentPostProcessor implements EnvironmentPostProcessor {

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
Map<String, Object> config = new HashMap<>();
config.put("taskpilot.tenant.names", "alpha,beta");
environment.getPropertySources()
.addFirst(new MapPropertySource("tenantConfig", config));
}
}

注册方式,传统写法:

1
2
3
# META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.taskpilot.TenantEnvironmentPostProcessor

Spring Boot 2.7+ 更推荐:

1
2
# META-INF/spring/org.springframework.boot.env.EnvironmentPostProcessor.imports
com.example.taskpilot.TenantEnvironmentPostProcessor

它适合做:

  • 从配置中心拉配置;
  • 解密配置;
  • 添加自定义 PropertySource;
  • 根据环境变量决定激活哪些配置。

再往后一点,ApplicationContext 已经创建,但 Bean 还没开始创建。
这时候用 ApplicationContextInitializer。

1
2
3
4
5
6
7
8
9
10
11
public class TenantContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {

@Override
public void initialize(ConfigurableApplicationContext context) {
context.getEnvironment().setActiveProfiles("tenant");

context.addApplicationListener(event ->
System.out.println("收到事件:" + event.getClass().getSimpleName()));
}
}

一句话区分:

接入点 时机 能做什么
EnvironmentPostProcessor ApplicationContext 之前 改 Environment
ApplicationContextInitializer context 创建后,refresh 前 初始化 context、激活 profile、加监听器

第二幕:图纸阶段,动态注册 BeanDefinition

Spring 启动时会先读配置类、扫描注解,得到一堆 BeanDefinition。

你可以把 BeanDefinition 理解成“造 Bean 的图纸”。
这时候 Bean 还没造出来。

TaskPilot 需要给每个租户注册一个数据源。
这种“往图纸柜里加图纸”的事,用 BeanDefinitionRegistryPostProcessor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
public class TenantDataSourceRegistrar
implements BeanDefinitionRegistryPostProcessor {

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
String tenants = "alpha,beta";
for (String tenant : tenants.split(",")) {
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(HikariDataSource.class);
bd.getPropertyValues().add("jdbcUrl",
"jdbc:mysql://localhost:3306/task_" + tenant);
bd.getPropertyValues().add("username", "root");
bd.getPropertyValues().add("password", "root");

registry.registerBeanDefinition("dataSource_" + tenant, bd);
}
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 这里可以留空
}
}

它比 BeanFactoryPostProcessor 更早执行,因为它能注册新的 BeanDefinition。

如果你只是想修改已经存在的图纸,比如把所有 Service 改成懒加载,用 BeanFactoryPostProcessor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class LazyServicePostProcessor implements BeanFactoryPostProcessor {

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
for (String name : beanFactory.getBeanDefinitionNames()) {
BeanDefinition bd = beanFactory.getBeanDefinition(name);
if (bd.getBeanClassName() != null
&& bd.getBeanClassName().endsWith("Service")) {
bd.setLazyInit(true);
}
}
}
}

记住这个比喻:

接入点 比喻 作用
BeanDefinitionRegistryPostProcessor 往图纸柜里加图纸 注册新的 BeanDefinition
BeanFactoryPostProcessor 改图纸 修改已有 BeanDefinition
BeanPostProcessor 改成品 修改已经创建出来的 Bean 实例

第三幕:造 Bean:Aware 是补给站,BeanPostProcessor 是加工厂

图纸有了,Spring 开始实例化 Bean。

一个 Bean 的完整生命周期,大致是这样:

1
2
3
4
5
6
7
8
9
10
11
12
构造方法
-> 属性注入 populateBean
-> Aware 回调
-> BeanPostProcessor.postProcessBeforeInitialization
-> @PostConstruct
-> InitializingBean.afterPropertiesSet
-> init-method
-> BeanPostProcessor.postProcessAfterInitialization
-> 使用
-> @PreDestroy
-> DisposableBean.destroy
-> destroy-method

这里最容易被忽略的是 Aware。

Aware:Bean 的补给站

Aware 是一组回调接口。
Bean 实现它们以后,Spring 会在创建 Bean 的过程中,把容器里的资源“塞”给它。

常见 Aware:

Aware 接口 注入什么 典型用途
BeanNameAware 当前 Bean 名称 日志、调试
BeanClassLoaderAware ClassLoader 类加载相关
BeanFactoryAware BeanFactory 拿 BeanFactory
EnvironmentAware Environment 读配置
EmbeddedValueResolverAware 占位符解析器 解析 ${}
ResourceLoaderAware ResourceLoader 加载资源文件
ApplicationEventPublisherAware 事件发布器 发布自定义事件
MessageSourceAware MessageSource 国际化
ApplicationContextAware ApplicationContext 拿整个容器
ServletContextAware ServletContext Web 环境

看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Component
public class TaskExecutor implements ApplicationContextAware,
ApplicationEventPublisherAware, InitializingBean {

private ApplicationContext context;
private ApplicationEventPublisher publisher;

@Override
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}

@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}

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

@Override
public void afterPropertiesSet() {
System.out.println("InitializingBean.afterPropertiesSet");
}

@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy");
}
}

Aware 不是用来“改变容器流程”的,它更像是 Bean 在说:

我准备好了,把容器里的工具给我一份,我后面要用。

BeanPostProcessor:Bean 的加工厂

TaskPilot 有个需求:
所有带 @Audit 的任务处理器,调用时都要记录耗时。

这种“对 Bean 实例做包装、做代理”的事情,用 BeanPostProcessor:

1
2
3
4
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audit {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class AuditBeanPostProcessor implements BeanPostProcessor {

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean.getClass().isAnnotationPresent(Audit.class)) {
return Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(proxy, method, args) -> {
long start = System.nanoTime();
try {
return method.invoke(bean, args);
} finally {
long cost = (System.nanoTime() - start) / 1_000_000;
System.out.printf("[Audit] %s.%s 耗时 %d ms%n",
beanName, method.getName(), cost);
}
});
}
return bean;
}
}

真实项目里,Spring AOP 的代理也主要发生在 BeanPostProcessor 阶段。
你熟悉的 @Transactional、@Async,背后都有它的影子。

如果想在代理里发事件,让被代理对象实现 ApplicationEventPublisherAware 即可。这就是 Aware 线和事件线的交汇:

Aware 让 Bean 拿到事件发布器,Bean 再通过事件发布器把消息广播出去。


第四幕:所有单例就绪,做全局校验

TaskPilot 有很多 TaskHandler,每个负责一种任务类型。

我们希望启动时检查:任务类型不能重复。

这个校验不能太早做,因为 Handler 可能还没创建完;也不能太晚,最好在应用真正对外服务前发现错误。

接入点:SmartInitializingSingleton。

1
2
3
4
public interface TaskHandler {
String type();
void handle(String task);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class TaskHandlerValidator implements SmartInitializingSingleton {

private final Map<String, TaskHandler> handlers;

public TaskHandlerValidator(Map<String, TaskHandler> handlers) {
this.handlers = handlers;
}

@Override
public void afterSingletonsInstantiated() {
Set<String> types = new HashSet<>();
handlers.forEach((name, handler) -> {
if (!types.add(handler.type())) {
throw new IllegalStateException(
"重复的任务类型: " + handler.type() + ",Bean: " + name);
}
});
}
}

afterSingletonsInstantiated() 执行时:

  • 所有非懒加载单例都已经创建完;
  • 但 ContextRefreshedEvent 还没发布;
  • 适合做全局校验、收集所有实现、建立映射关系。

第五幕:事件——一个判断就够了

Spring 里事件很多,但真正需要记住的只有一个判断:

事件发布的时候,Bean 存在吗?

存在 → @EventListener 随便用。 不存在 → @EventListener 根本收不到,必须用 SpringApplication.addListeners 或 spring.factories。

为什么?因为 @EventListener 依赖 Bean 被创建、事件多播器初始化、监听器被注册,这些都在 refresh() 里发生。而 Spring Boot 的早期事件,发生在 refresh() 之前。

1
2
3
4
5
6
7
8
9
10
ApplicationStartingEvent             ← Bean 不存在,@EventListener 收不到
ApplicationEnvironmentPreparedEvent ← Bean 不存在,@EventListener 收不到
ApplicationContextInitializedEvent ← Bean 不存在,@EventListener 收不到
ApplicationPreparedEvent ← Bean 不存在,@EventListener 收不到
────────────────────── refresh() ──────────────────────
ContextRefreshedEvent ← Bean 已存在,@EventListener 能收到
ApplicationStartedEvent ← Bean 已存在,@EventListener 能收到
ApplicationReadyEvent ← Bean 已存在,@EventListener 能收到
────────────────────── 关闭 ──────────────────────
ContextClosedEvent ← Bean 已存在,@EventListener 能收到

早期事件只能用 ApplicationListener + 手动注册:

1
2
3
4
5
6
7
8
9
10
public class TenantEnvListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// 这里能拿到 Environment,但拿不到 Bean
ConfigurableEnvironment env = event.getEnvironment();
System.out.println("租户配置可以读了");
}
}

注册:

1
2
3
SpringApplication app = new SpringApplication(TaskPilotApplication.class);
app.addListeners(new TenantEnvListener());
app.run(args);

框架级扩展用 spring.factories:

1
2
org.springframework.context.ApplicationListener=\
com.example.taskpilot.TenantEnvListener

后期事件直接用 @EventListener:

1
2
3
4
5
6
7
8
@Component
public class CacheWarmer {

@EventListener(ApplicationReadyEvent.class)
public void warmUp() {
System.out.println("应用已就绪,开始预热缓存");
}
}

要发事件,注入 ApplicationEventPublisher:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class AuditPublisher {

private final ApplicationEventPublisher publisher;

public AuditPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}

public void publish(String beanName, String method, long cost) {
publisher.publishEvent(new TaskAuditEvent(this, beanName, method, cost));
}
}

事件默认同步。想异步,@Async 加在 @EventListener 上,或换一个带 TaskExecutor 的 ApplicationEventMulticaster。

一句话:

事件能不能被 @EventListener 收到,只看发布时 Bean 出生了没有。


第六幕:容器刷新完成,开始对外服务

所有单例就绪后,Spring 会启动 SmartLifecycle,然后发布 ContextRefreshedEvent。

再往后是 Spring Boot 的启动事件:

1
2
3
4
5
6
SmartLifecycle.start
-> ContextRefreshedEvent
-> afterRefresh()
-> ApplicationStartedEvent
-> ApplicationRunner / CommandLineRunner
-> ApplicationReadyEvent

TaskPilot 需要在应用就绪后预热缓存。

用 @EventListener:

1
2
3
4
5
6
7
8
@Component
public class CacheWarmer {

@EventListener(ApplicationReadyEvent.class)
public void warmUp() {
System.out.println("应用已就绪,开始预热缓存");
}
}

也可以用 ApplicationRunner:

1
2
3
4
5
6
7
8
@Component
public class AdminInitializer implements ApplicationRunner {

@Override
public void run(ApplicationArguments args) {
System.out.println("初始化管理员账号");
}
}

区别:

接入点 时机 适合
ApplicationRunner ApplicationReadyEvent 之前 启动后执行一次,带结构化参数
CommandLineRunner 同上 启动后执行一次,参数是 String...
@EventListener(ApplicationReadyEvent.class) ApplicationRunner 之后 应用完全就绪后做事

如果只是监听 Spring 容器事件,用 ContextRefreshedEvent;如果是 Spring Boot 应用,推荐 ApplicationReadyEvent。


第七幕:运行与关闭,体面离场

TaskPilot 有个 MQ 消费者,需要:

  • 容器启动后开始消费;
  • 容器关闭时先停止消费;
  • 等待正在执行的任务完成;
  • 再关闭线程池。

这种“随容器启停”的组件,用 SmartLifecycle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Component
public class MqConsumerLifecycle implements SmartLifecycle {

private volatile boolean running = false;

@Override
public void start() {
running = true;
System.out.println("启动 MQ 消费者");
}

@Override
public void stop() {
System.out.println("优雅停止 MQ 消费者");
running = false;
}

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

@Override
public int getPhase() {
return 0;
}
}

phase 越小,启动越早,停止越晚。比如:

  • 数据源连接池:phase = 0,最早启动,最晚关闭;
  • MQ 消费者:phase = 100,晚点启动,早点停止;
  • Web 服务器:phase = Integer.MAX_VALUE,最后启动,最先停止。

关闭阶段顺序:

1
2
3
4
5
ContextClosedEvent
-> SmartLifecycle.stop
-> @PreDestroy
-> DisposableBean.destroy
-> destroy-method

所以优雅停机可以这样写:

1
2
3
4
5
6
7
8
@Component
public class GracefulShutdown {

@PreDestroy
public void shutdown() {
System.out.println("等待任务完成,关闭线程池");
}
}

注意:ContextClosedEvent 发布时,SmartLifecycle.stop 还没执行。
所以资源停止优先用 SmartLifecycle,最后的清理用 @PreDestroy 或 DisposableBean。


一张地图:Spring 接入点全景

阶段 接入点 一句话 TaskPilot 例子
配置环境 EnvironmentPostProcessor 改 Environment 从配置中心拉租户配置
早期事件 ApplicationListener + addListeners 监听 Bean 出生前事件 监听 ApplicationEnvironmentPreparedEvent
初始化上下文 ApplicationContextInitializer refresh 前改 context 激活 tenant profile
加图纸 BeanDefinitionRegistryPostProcessor 注册 BeanDefinition 动态注册租户数据源
改图纸 BeanFactoryPostProcessor 修改 BeanDefinition 把 Service 改成懒加载
改成品 BeanPostProcessor 修改 Bean 实例 给 @Audit 加代理
拿容器资源 Aware 注入容器资源 ApplicationContextAware、ApplicationEventPublisherAware
初始化 @PostConstruct / InitializingBean / init-method Bean 初始化 初始化连接池
全局校验 SmartInitializingSingleton 所有单例创建完 校验任务类型唯一
容器就绪 ContextRefreshedEvent 容器刷新完成 预热本地缓存
启动后一次 ApplicationRunner / CommandLineRunner Boot 启动后执行 初始化管理员账号
应用就绪 ApplicationReadyEvent 可以对外服务 注册到注册中心
事件监听 ApplicationListener / @EventListener 听广播 审计事件、租户事件
事件发布 ApplicationEventPublisher 发广播 发布 TaskAuditEvent
启停组件 Lifecycle / SmartLifecycle 随容器启停 MQ 消费者优雅启停
销毁 @PreDestroy / DisposableBean / destroy-method 释放资源 关闭线程池
条件装配 Condition / @Conditional 按条件注册 只有多租户模式才加载
配置导入 ImportSelector / ImportBeanDefinitionRegistrar @EnableXxx 底层 自定义 @EnableTaskPilot

最后送你一个决策树

以后写代码前,先问自己:

1
2
3
4
5
6
7
8
9
10
11
12
13
要改配置环境?          -> EnvironmentPostProcessor
要监听早期事件? -> ApplicationListener + addListeners / spring.factories
要初始化 context? -> ApplicationContextInitializer
要新增 BeanDefinition? -> BeanDefinitionRegistryPostProcessor
要修改 BeanDefinition? -> BeanFactoryPostProcessor
要修改 Bean 实例? -> BeanPostProcessor
要拿容器资源? -> Aware
要所有单例创建完做校验? -> SmartInitializingSingleton
要监听事件? -> ApplicationListener / @EventListener
要发事件? -> ApplicationEventPublisher
随容器启停? -> SmartLifecycle
启动后执行一次? -> ApplicationRunner / CommandLineRunner
销毁清理? -> @PreDestroy / DisposableBean

口诀:

环境上下文,定义实例化;
Aware 拿资源,前后处理器;
单例完成后,刷新发事件;
监听与发布,启停生命周期;
Boot 有 Runner,关闭做销毁。

现在再看到 BeanPostProcessor,你不会觉得它只是一个名词。
你会知道:它站在“Bean 已经造出来,正在初始化”的那个路口。

现在再看到 ApplicationListener,你不会觉得它只是“监听器”。
你会知道:它是容器一生广播系统里的收音机,而且——

它能不能收到,只看发布的那一刻 Bean 出生了没有。

现在再看到 ApplicationContextAware,你不会觉得它只是“一个接口”。
你会知道:它是 Bean 在生命周期中领取容器资源的补给站。

Spring 接入点不是一张名词表,而是一条时间线。
记住容器走到哪一步,你就知道该用谁。
记住事件在广播什么,你就知道该听什么。
记住 Bean 在领取什么,你就知道该拿什么。