尽管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("发动次序:post-construct");
}
public void initMethod() {
log.error("发动次序:init-method");
}
- 新建 TestSpringOrder2 承继
public class TestSpringOrder3 implements
BeanPostProcessor,
BeanFactoryPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.error("发动次序:BeanPostProcessor postProcessBeforeInitialization beanName:{}", beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
log.error("发动次序:BeanPostProcessor postProcessAfterInitialization beanName:{}", beanName);
return bean;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
log.error("发动次序:BeanFactoryPostProcessor postProcessBeanFactory ");
}
}
履行以上代码后,能够在日志中看到发动次序!
实践的履行次序
2023-11-25 18:10:53,748 [main] ERROR (TestSpringOrder3:37) - 发动次序:BeanFactoryPostProcessor postProcessBeanFactory
2023-11-25 18:10:59,299 [main] ERROR (TestSpringOrder:53) - 发动次序:构造函数 TestSpringOrder
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder:127) - 发动次序: Autowired
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder:129) - 发动次序:setBeanName
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder:111) - 发动次序:setBeanFactory
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder:121) - 发动次序:setApplicationContext
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder3:25) - 发动次序:BeanPostProcessor postProcessBeforeInitialization beanName:testSpringOrder
2023-11-25 18:10:59,316 [main] ERROR (TestSpringOrder:63) - 发动次序:post-construct
2023-11-25 18:10:59,317 [main] ERROR (TestSpringOrder:116) - 发动次序:afterPropertiesSet
2023-11-25 18:10:59,317 [main] ERROR (TestSpringOrder:46) - 发动次序:init-method
2023-11-25 18:10:59,320 [main] ERROR (TestSpringOrder3:31) - 发动次序:BeanPostProcessor postProcessAfterInitialization beanName:testSpringOrder
2023-11-25 18:17:21,563 [main] ERROR (SpringOrderConfiguartion:21) - 发动次序: @Bean 注解办法履行
2023-11-25 18:17:21,668 [main] ERROR (TestSpringOrder:58) - 发动次序:SmartInitializingSingleton
2023-11-25 18:17:21,675 [main] ERROR (TestSpringOrder:74) - 发动次序:start
2023-11-25 18:17:23,508 [main] ERROR (TestSpringOrder:68) - 发动次序:ContextRefreshedEvent
2023-11-25 18:17:23,574 [main] ERROR (TestSpringOrder:79) - 发动次序:CommandLineRunner
我经过在以上扩展点 增加 debug 断点,调试代码,整理出 Spring发动原理的 长图。进程省掉…………
一张长图透彻解说 Spring发动次序
实例化和初始化的差异
new TestSpringOrder(); new 创立目标实例,即为实例化一个目标;履行该Bean的 init-method 等办法 为初始化一个Bean。留意初始化和实例化的差异。
Spring 重要扩展点的发动次序
-
BeanFactoryPostProcessor
BeanFactory初始化之后,一切的Bean界说现已被加载,但Bean实例还没被创立(不包含BeanFactoryPostProcessor
类型)。Spring IoC容器答应BeanFactoryPostProcessor读取装备元数据,修正bean的界说,Bean的特点值等。 -
实例化Bean
Spring 调用java反射API 实例化 Bean。 等同于 new TestSpringOrder();
-
Autowired 安装依靠
Autowired是 借助于 AutowiredAnnotationBeanPostProcessor 解析 Bean 的依靠,安装依靠。假如被依靠的Bean还未初始化,则先初始化 被依靠的Bean。在 Bean实例化完结后,Spring将首要安装Bean依靠的特点。
-
BeanNameAware setBeanName
-
BeanFactoryAware setBeanFactory
-
ApplicationContextAware setApplicationContext
在Bean实例化前,会首先设置Aware接口,例如 BeanNameAware BeanFactoryAware ApplicationContextAware 等
-
BeanPostProcessor postProcessBeforeInitialization
假如我想在 bean初始化办法前后要增加一些自己逻辑处理。能够供给 BeanPostProcessor接口完结类,然后注册到Spring IoC容器中。在此接口中,能够创立Bean的代理,乃至替换这个Bean。
-
PostConstruct 履行
接下来 Spring会依次调用 Bean实例初始化的 三大办法。
-
InitializingBean afterPropertiesSet
-
init-method 办法履行
-
BeanPostProcessor postProcessAfterInitialization
在 Spring 对Bean的初始化办法履行完结后,履行该办法
-
其他Bean 实例化和初始化
Spring 会循环初始化Bean。直至一切的单例Bean都完结初始化
-
一切单例Bean 初始化完结后
-
SmartInitializingSingleton Bean实例化后置处理
该接口的履行机遇在 一切的单例Bean履行完结后。例如Spring 事情订阅机制的 EventListener注解,一切的订阅者 都是 在这个方位被注册进 Spring的。而在此之前,Spring Event订阅机制还未初始化完结。所以假如有 MQ、Rpc 进口流量在此之前敞开,Spring Event就或许出问题!
所以强烈主张 Http、MQ、Rpc 进口流量在 SmartInitializingSingleton 之后敞开流量。
Http、MQ、Rpc 进口流量必须在 SmartInitializingSingleton 之后敞开流量。
- SmartLifecyle smart start 办法履行 Spring 供给的扩展点,在一切单例Bean的 EventListener等组件悉数发动完结后,即Spring发动完结,则履行 start 办法。在这个方位合适敞开进口流量!
Http、MQ、Rpc 进口流量合适 在 SmartLifecyle 中敞开
-
发布 ContextRefreshedEvent 办法 该事情会履行屡次,在 Spring Refresh 履行完结后,就会发布该事情!
-
注册和初始化 Spring MVC
SpringBoot 运用,在父级 Spring发动完结后,会测验发动 内嵌式 tomcat容器。在此之前,SpringBoot会初始化 SpringMVC 和注册DispatcherServlet到Web容器。
-
Tomcat/Jetty 容器敞开端口
SpringBoot 调用内嵌式容器,会敞开并监听端口,此刻Http流量就敞开了。
-
运用发动完结后,履行 CommandLineRunner
SpringBoot 特有的机制,待一切的彻底履行完结后,会履行该接口 run办法。值得一提的是,由于此刻Http流量现已敞开,假如此刻进行本地缓存初始化、预热缓存等,略微有些晚了! 在这个距离期,或许缓存还未安排妥当!
所以预热缓存的机遇应该发生在 进口流量敞开之前,比较合适的机会是在 Bean初始化的阶段。尽管 在Bean初始化时 Spring没有完结发动,可是调用 Bean预热缓存也是能够的。可是留意:不要在 Bean初始化时 运用 Spring Event,由于它还未完结初始化 。
回答 关于 Spring 发动原理的若干问题
- init-method、PostConstruct、afterPropertiesSet 三个办法的履行次序。
回答: PostConstruct,afterPropertiesSet,init-method
- 有两个 Bean声明晰初始化办法。 A运用 PostConstruct注解声明,B运用 init-method 声明。Spring必定先履行 A 的PostConstruct 办法吗?
回答:Spring 会循环初始化Bean实例,初始化完结1个Bean,再初始化下一个Bean。Spring并没有运用这种机制发动,即一切的Bean先履行 PostConstruct,再一致履行afterProperfiesSet。 此外,A、B两个Bean的初始化次序不确定,谁先谁后不确定。无法确保 A 的PostConstruct 必定先履行。除非运用 Order注解,声明Bean的初始化次序!
- Spring 何时安装Autowire特点,PostConstruct办法中引证 Autowired 字段是否会空指针?
Autowired安装依靠发生在 PostConstruct之前,不会呈现空指针!
- PostConstruct 中办法依靠ApplicationContextAware拿到 ApplicationContext,两者的次序谁先谁后?是否会呈现空指针!
ApplicationContextAware 会先履行,不会呈现空指针!可是当Autowired没有找到对应的依靠,并且声明晰非强制依靠时,该字段会为空,有潜在 空指针风险。
- 项目应该怎么监听 Spring 的发动安排妥当事情。
经过SmartLifecyle start办法,监听Spring安排妥当 。合适在此敞开进口流量!
- 项目怎么监听Spring 改写事情。
监听 Spring Event ContextRefreshedEvent
- Spring安排妥当事情和改写事情的履行次序和差异。
Spring安排妥当事情会先于 改写事情。两者都或许屡次履行,要确保办法的幂等处理,避免重复注册问题
- Http 流量进口何时发动完结。
SpringBoot 最终阶段,发动完结Spring 上下文,才敞开Http进口流量,此刻 SmartLifecycle#start 已履行。一切单例Bean和SpringEvent等组件都现已安排妥当!
- 项目中在 init-method 办法中注册 Rpc是否合理?什么是合理的机遇?
init 敞开Rpc流量十分不合理。由于Spring没有发动完结,包含 Spring Event没有安排妥当!
- 项目中在 init-method 办法中注册 MQ消费组是否合理? 什么是合理的机遇?
init 敞开 MQ 流量十分不合理。由于Spring没有发动完结,包含 Spring Event没有安排妥当!
- Spring还未彻底发动,在 PostConstruct 中调用 getBeanByAnnotation能否获得精确的结果?
尽管未发动完结,可是Spring履行该getBeanByAnnotation办法时,会首先检查 Bean界说,假如Bean界说对应的 Bean没有初始化,则初始化这些Bean。所以即便是Spring初始化进程中调用,调用结果是精确的。
源码等级介绍
SmartInitializingSingleton 接口的履行方位
下图代码阐明晰,Spring在初始化悉数 单例Bean以后,会履行 SmartInitializingSingleton 接口。
Autowired 何时安装Bean的依靠
在Bean实例化之后,但初始化之前,AutowiredAnnotationBeanPostProcessor
会注入Autowired字段。
SpringBoot 何时敞开Http端口
下图代码中能够看到,SpringBoot会首要发动 Spring上下文,完结后才发动 嵌入式Web容器,初始化SpringMVC,监听端口
Spring 初始化Bean的要害代码
下图我加了注释,Spring初始化Bean的要害代码,全在 这个办法里,感兴趣的能够自行查阅代码 。 AbstractAutowireCapableBeanFactory#initializeBean
Spring CommandLineRunner 履行方位
Spring Boot外部,当发动完Spring上下文以后,最终才发动 CommandLineRunner。
总结
SpringBoot 会在Spring彻底发动完结后,才敞开Http流量。这给了我们启示:应该在Spring发动完结后敞开进口流量。Rpc和 MQ流量 也应该如此,所以主张大家 在 SmartLifecype 或许 ContextRefreshedEvent 等方位 注册服务,敞开流量。
例如 Spring Cloud Eureka
服务发现组件,就是在 SmartLifecype中注册服务的!
整理 10 个小时写完本篇文章,希望大家有所收获。[抱拳]