尽管Java程序员大部分作业都是CRUD,可是作业中常用的中间件必须和Spring集成,假如不知道Spring的原理,很难了解这些中间件和结构的原理。

一张长图透彻解说 Spring发动次序

一张长图透彻了解SpringBoot 发动原理,架构师必备常识,不为敷衍面试!

测验对Spring发动原理的了解程度

我举个比如,测验一下,你对Spring发动原理的了解程度。

  1. Rpc结构和Spring的集成问题。Rpc结构何时注册露出服务,在哪个Spring扩展点注册呢?init-method 中行不可?

  2. MQ 消费组和Spring的集成问题。MQ顾客何时开端消费,在哪个Spring扩展点”注册“自己?init-method 中行不可?

  3. 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发动原理的重要性。

  1. Spring还未彻底发动,在 PostConstruct 中调用 getBeanByAnnotation 能否获得精确的结果?
  2. 项目应该怎么监听 Spring 的发动安排妥当事情?
  3. 项目怎么监听Spring 改写事情?
  4. Spring安排妥当事情和改写事情的履行次序和差异?
  5. Http 流量进口何时发动完结?
  6. 项目中在 init-method 办法中注册 Rpc 是否合理?什么是合理的机遇?
  7. 项目中在 init-method 办法中注册 MQ 消费组是否合理? 什么是合理的机遇?
  8. PostConstruct 中办法依靠ApplicationContextAware拿到 ApplicationContext,两者的次序谁先谁后?是否会呈现空指针!
  9. init-method、PostConstruct、afterPropertiesSet 三个办法的履行次序?
  10. 有两个 Bean声明晰初始化办法。 A运用 PostConstruct注解声明,B运用 init-method 声明。Spring必定先履行 A 的PostConstruct 办法吗?
  11. Spring 何时安装Autowire特点,PostConstruct 办法中引证 Autowired 字段什么场景会空指针?

通晓Spring 发动原理,以上问题则迎刃而解。接下来,请大家和五哥,一起学习Spring的发动原理,看看Spring的扩展点别离在何时履行。

一起数数 Spring发动进程的扩展点有几个?

Spring的扩展点极多,这儿为了讲清楚发动原理,所以只列举和发动进程有关的扩展点。

  1. BeanFactoryAware 可在Bean 中获取 BeanFactory 实例
  2. ApplicationContextAware 可在Bean 中获取 ApplicationContext 实例
  3. BeanNameAware 能够在Bean中得到它在IOC容器中的Bean的实例的名字。
  4. ApplicationListener 可监听 ContextRefreshedEvent等。
  5. CommandLineRunner 整个项目发动完毕后,主动履行
  6. SmartLifecycle#start 在Spring Bean实例化完结后,履行start 办法。
  7. 运用@PostConstruct注解,用于Bean实例初始化
  8. 完结InitializingBean接口,用于Bean实例初始化
  9. xml 中声明 init-method 办法,用于Bean实例初始化
  10. Configuration 装备类 经过@Bean注解 注册Bean到Spring
  11. BeanPostProcessor 在Bean的初始化前后,植入扩展点!
  12. BeanFactoryPostProcessor 在BeanFactory创立后植入 扩展点!

经过打印日志学习Spring的履行次序

首要我们先经过 代码实验,验证一下以上扩展点的履行次序。

  1. 声明 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");
}
  1. TestSpringOrder 运用 PostConstruct注解初始化,声明 init-method办法初始化。
@PostConstruct
public void postConstruct() {
   log.error("发动次序:post-construct");
}
public void initMethod() {
   log.error("发动次序:init-method");
}
  1. 新建 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发动次序

一张长图透彻了解SpringBoot 发动原理,架构师必备常识,不为敷衍面试!

实例化和初始化的差异

new TestSpringOrder(); new 创立目标实例,即为实例化一个目标;履行该Bean的 init-method 等办法 为初始化一个Bean。留意初始化和实例化的差异。

Spring 重要扩展点的发动次序

  1. BeanFactoryPostProcessor
    BeanFactory初始化之后,一切的Bean界说现已被加载,但Bean实例还没被创立(不包含BeanFactoryPostProcessor类型)。Spring IoC容器答应BeanFactoryPostProcessor读取装备元数据,修正bean的界说,Bean的特点值等。

  2. 实例化Bean

    Spring 调用java反射API 实例化 Bean。 等同于 new TestSpringOrder();

  3. Autowired 安装依靠

    Autowired是 借助于 AutowiredAnnotationBeanPostProcessor 解析 Bean 的依靠,安装依靠。假如被依靠的Bean还未初始化,则先初始化 被依靠的Bean。在 Bean实例化完结后,Spring将首要安装Bean依靠的特点。

  4. BeanNameAware setBeanName

  5. BeanFactoryAware setBeanFactory

  6. ApplicationContextAware setApplicationContext

    在Bean实例化前,会首先设置Aware接口,例如 BeanNameAware BeanFactoryAware ApplicationContextAware 等

  7. BeanPostProcessor postProcessBeforeInitialization

    假如我想在 bean初始化办法前后要增加一些自己逻辑处理。能够供给 BeanPostProcessor接口完结类,然后注册到Spring IoC容器中。在此接口中,能够创立Bean的代理,乃至替换这个Bean。

  8. PostConstruct 履行

    接下来 Spring会依次调用 Bean实例初始化的 三大办法。

  9. InitializingBean afterPropertiesSet

  10. init-method 办法履行

  11. BeanPostProcessor postProcessAfterInitialization

    在 Spring 对Bean的初始化办法履行完结后,履行该办法

  12. 其他Bean 实例化和初始化

    Spring 会循环初始化Bean。直至一切的单例Bean都完结初始化

  13. 一切单例Bean 初始化完结后

  14. SmartInitializingSingleton Bean实例化后置处理

    该接口的履行机遇在 一切的单例Bean履行完结后。例如Spring 事情订阅机制的 EventListener注解,一切的订阅者 都是 在这个方位被注册进 Spring的。而在此之前,Spring Event订阅机制还未初始化完结。所以假如有 MQ、Rpc 进口流量在此之前敞开,Spring Event就或许出问题!

    所以强烈主张 Http、MQ、Rpc 进口流量在 SmartInitializingSingleton 之后敞开流量。

Http、MQ、Rpc 进口流量必须在 SmartInitializingSingleton 之后敞开流量。

  1. SmartLifecyle smart start 办法履行 Spring 供给的扩展点,在一切单例Bean的 EventListener等组件悉数发动完结后,即Spring发动完结,则履行 start 办法。在这个方位合适敞开进口流量!

Http、MQ、Rpc 进口流量合适 在 SmartLifecyle 中敞开

  1. 发布 ContextRefreshedEvent 办法 该事情会履行屡次,在 Spring Refresh 履行完结后,就会发布该事情!

  2. 注册和初始化 Spring MVC

    SpringBoot 运用,在父级 Spring发动完结后,会测验发动 内嵌式 tomcat容器。在此之前,SpringBoot会初始化 SpringMVC 和注册DispatcherServlet到Web容器。

  3. Tomcat/Jetty 容器敞开端口

    SpringBoot 调用内嵌式容器,会敞开并监听端口,此刻Http流量就敞开了。

  4. 运用发动完结后,履行 CommandLineRunner

    SpringBoot 特有的机制,待一切的彻底履行完结后,会履行该接口 run办法。值得一提的是,由于此刻Http流量现已敞开,假如此刻进行本地缓存初始化、预热缓存等,略微有些晚了! 在这个距离期,或许缓存还未安排妥当!

    所以预热缓存的机遇应该发生在 进口流量敞开之前,比较合适的机会是在 Bean初始化的阶段。尽管 在Bean初始化时 Spring没有完结发动,可是调用 Bean预热缓存也是能够的。可是留意:不要在 Bean初始化时 运用 Spring Event,由于它还未完结初始化 。

回答 关于 Spring 发动原理的若干问题

  1. init-method、PostConstruct、afterPropertiesSet 三个办法的履行次序。

回答: PostConstruct,afterPropertiesSet,init-method

  1. 有两个 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的初始化次序!

  1. Spring 何时安装Autowire特点,PostConstruct办法中引证 Autowired 字段是否会空指针?

Autowired安装依靠发生在 PostConstruct之前,不会呈现空指针!

  1. PostConstruct 中办法依靠ApplicationContextAware拿到 ApplicationContext,两者的次序谁先谁后?是否会呈现空指针!

ApplicationContextAware 会先履行,不会呈现空指针!可是当Autowired没有找到对应的依靠,并且声明晰非强制依靠时,该字段会为空,有潜在 空指针风险。

  1. 项目应该怎么监听 Spring 的发动安排妥当事情。

经过SmartLifecyle start办法,监听Spring安排妥当 。合适在此敞开进口流量!

  1. 项目怎么监听Spring 改写事情。

监听 Spring Event ContextRefreshedEvent

  1. Spring安排妥当事情和改写事情的履行次序和差异。

Spring安排妥当事情会先于 改写事情。两者都或许屡次履行,要确保办法的幂等处理,避免重复注册问题

  1. Http 流量进口何时发动完结。

SpringBoot 最终阶段,发动完结Spring 上下文,才敞开Http进口流量,此刻 SmartLifecycle#start 已履行。一切单例Bean和SpringEvent等组件都现已安排妥当!

  1. 项目中在 init-method 办法中注册 Rpc是否合理?什么是合理的机遇?

init 敞开Rpc流量十分不合理。由于Spring没有发动完结,包含 Spring Event没有安排妥当!

  1. 项目中在 init-method 办法中注册 MQ消费组是否合理? 什么是合理的机遇?

init 敞开 MQ 流量十分不合理。由于Spring没有发动完结,包含 Spring Event没有安排妥当!

  1. Spring还未彻底发动,在 PostConstruct 中调用 getBeanByAnnotation能否获得精确的结果?

尽管未发动完结,可是Spring履行该getBeanByAnnotation办法时,会首先检查 Bean界说,假如Bean界说对应的 Bean没有初始化,则初始化这些Bean。所以即便是Spring初始化进程中调用,调用结果是精确的。

源码等级介绍

SmartInitializingSingleton 接口的履行方位

下图代码阐明晰,Spring在初始化悉数 单例Bean以后,会履行 SmartInitializingSingleton 接口。

一张长图透彻了解SpringBoot 发动原理,架构师必备常识,不为敷衍面试!

Autowired 何时安装Bean的依靠

在Bean实例化之后,但初始化之前,AutowiredAnnotationBeanPostProcessor 会注入Autowired字段。

一张长图透彻了解SpringBoot 发动原理,架构师必备常识,不为敷衍面试!

SpringBoot 何时敞开Http端口

下图代码中能够看到,SpringBoot会首要发动 Spring上下文,完结后才发动 嵌入式Web容器,初始化SpringMVC,监听端口

一张长图透彻了解SpringBoot 发动原理,架构师必备常识,不为敷衍面试!

Spring 初始化Bean的要害代码

下图我加了注释,Spring初始化Bean的要害代码,全在 这个办法里,感兴趣的能够自行查阅代码 。 AbstractAutowireCapableBeanFactory#initializeBean

一张长图透彻了解SpringBoot 发动原理,架构师必备常识,不为敷衍面试!

Spring CommandLineRunner 履行方位

Spring Boot外部,当发动完Spring上下文以后,最终才发动 CommandLineRunner。

一张长图透彻了解SpringBoot 发动原理,架构师必备常识,不为敷衍面试!
一张长图透彻了解SpringBoot 发动原理,架构师必备常识,不为敷衍面试!

总结

SpringBoot 会在Spring彻底发动完结后,才敞开Http流量。这给了我们启示:应该在Spring发动完结后敞开进口流量。Rpc和 MQ流量 也应该如此,所以主张大家 在 SmartLifecype 或许 ContextRefreshedEvent 等方位 注册服务,敞开流量。

例如 Spring Cloud Eureka 服务发现组件,就是在 SmartLifecype中注册服务的!

整理 10 个小时写完本篇文章,希望大家有所收获。[抱拳]