虽然Java程序员大部分工作都是CRUD,但是工作中常用的中间件必须和Spring集成,如果不知道Spring的原理,很难理解这些中间件和框架的原理。
一张长图透彻解释 Spring启动顺序
测试对Spring启动原理的理解程度
我举个例子,测试一下,你对Spring启动原理的理解程度。
- Rpc框架和Spring的集成问题。Rpc框架何时注册暴露服务,在哪个Spring扩展点注册呢?init-method 中行不行?
- MQ 消费组和Spring的集成问题。MQ消费者何时开始消费,在哪个Spring扩展点”注册“自己?init-method 中行不行?
- SpringBoot集成Tomcat问题。如果出现已开启Http流量,Spring还未启动完成,怎么办?Tomcat何时开启端口,对外服务?
SpringBoot项目常见的流量入口无外乎 Rpc、Http、MQ 三种方式。一名合格的架构师必须精通服务的入口流量何时开启,如何正确开启?最近我遇到的两次线上故障都和Spring启动过程相关。(点击这里了解故障)
故障的具体表现是:Kafka消费组已经开始消费,已开启流量,然而Spring 还未启动完成。因为业务代码中使用的Spring Event事件订阅组件还未启动(订阅者还未注册到Spring),所以处理异常,出了线上故障。
根本原因是————项目在错误的时机开启 MQ 流量,然而Spring还未启动完成,导致出现故障。
正确的做法是:项目在Spring启动完成后开启入口流量,然而我司的Kafka消费组 在Spring init-method bean 实例化阶段就开启了流量,导致故障发生。
出现这样的问题,说明项目初期的程序员没有深入理解Spring的启动原理。
接下来,我再次抛出 11 个问题,说明这个问题————深入理解Spring启动原理的重要性。
- Spring还未完全启动,在 PostConstruct 中调用 getBeanByAnnotation 能否获得准确的结果?
- 项目应该如何监听 Spring 的启动就绪事件?
- 项目如何监听Spring 刷新事件?
- Spring就绪事件和刷新事件的执行顺序和区别?
- Http 流量入口何时启动完成?
- 项目中在 init-method 方法中注册 Rpc 是否合理?什么是合理的时机?
- 项目中在 init-method 方法中注册 MQ 消费组是否合理?什么是合理的时机?
- PostConstruct 中方法依赖ApplicationContextAware拿到ApplicationContext,两者的顺序谁先谁后?是否会出现空指针!
- init-method、PostConstruct、afterPropertiesSet 三个方法的执行顺序?
- 有两个 Bean声明了初始化方法。A使用 PostConstruct注解声明,B使用 init-method声明。Spring一定先执行 A 的PostConstruct 方法吗?
- Spring 何时装配Autowire属性,PostConstruct 方法中引用 Autowired 字段什么场景会空指针?
精通Spring 启动原理,以上问题则迎刃而解。接下来,请大家和我,一起学习Spring的启动原理,看看Spring的扩展点分别在何时执行。
一起数数 Spring启动过程的扩展点有几个?
Spring的扩展点极多,这里为了讲清楚启动原理,所以只列举和启动过程有关的扩展点。
- BeanFactoryAware 可在Bean 中获取 BeanFactory 实例
- ApplicationContextAware 可在Bean 中获取 ApplicationContext 实例
- BeanNameAware 可以在Bean中得到它在IOC容器中的Bean的实例的名字。
- ApplicationListener 可监听 ContextRefreshedEvent等。
- CommandLineRunner 整个项目启动完毕后,自动执行
- SmartLifecycle#start 在Spring Bean实例化完成后,执行start 方法。
- 使用@PostConstruct注解,用于Bean实例初始化
- 实现InitializingBean接口,用于Bean实例初始化
- xml 中声明 init-method 方法,用于Bean实例初始化
- Configuration 配置类 通过@Bean注解 注册Bean到Spring
- BeanPostProcessor 在Bean的初始化前后,植入扩展点!
- BeanFactoryPostProcessor 在BeanFactory创建后植入 扩展点!
通过打印日志学习Spring的执行顺序
首先我们先通过 代码实验,验证一下以上扩展点的执行顺序。
- 声明 TestSpringOrder 分别继承以下接口,并且在接口方法实现中,日志打印该接口的名称。
public class TestSpringOrder implements
ApplicationContextAware,
BeanFactoryAware,
InitializingBean,
SmartLifecycle,
BeanNameAware,
ApplicationListener<ContextRefreshedEvent>,
CommandLineRunner,
SmartInitializingSingleton {
@Override
public void afterPropertiesSet() throws Exception {
log.error("启动顺序:afterPropertiesSet");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.error("启动顺序:setApplicationContext");
}
- TestSpringOrder 使用 PostConstruct注解初始化,声明 init-method方法初始化。
@PostConstruct
public void postConstruct() {
log.error("启动顺序:po