【SpringBoot】启动原理源码剖析(含加载spring.factories扩展点)


相关文章:
Spring Boot 配置优先级顺序 源码中有配置参数加载顺序,正好参考一下
【SpringBoot】扩展机制之Spring Factories 扩展点理论知识
【SpringBoot】启动原理源码剖析(加载spring.factories扩展点) 含有扩展点的具体应用
【微服务专题】02-Spring Boot启动原理核心源码剖析 更详细的一篇文章

前言

【微服务专题】02-Spring Boot启动原理核心源码剖析 更详细的一篇文章,可以结合一起看

首先思考几个问题:
1.启动入口是哪个类?
2.配置文件或配置的属性是在哪加载的?如何定义优先级顺序的?

3.springboot下如果依赖多个模块,那么这些模块是如何被加载进来的,即扩展点具体怎么应用的?
答:在入口配置类上添加@SpringBootApplication标签,间接引用 @EnableAutoConfiguration,后者会扫描并加载扩展点下的EnableAutoConfiguration接口的实现类集合,这样实现了新的模块被加载

4.扩展点里面有很多接口,这些接口一般是何时被调用? 答案是 通过启动后在不同阶段出发不同的事件,存在很多监听器,监听到事件后会调用相应的接口

1. 概念&原理

在这里插入图片描述

springboot采用ServletWebServerApplicationContext完成加载上下文,该类功能类似ClassPathXmlApplicationContext。

2 源码

2.1 SpringBoot 事件监听器发布顺序

1.ApplicationStartingEvent在运行开始时发送,但在进行任何处理之前(侦听器和初始化程序的注册除外)发送。
2.在创建上下文之前,将发送ApplicationEnvironmentPreparedEvent3.准备ApplicationContext并调用ApplicationContextInitializers之后,将发送ApplicationContextInitializedEvent4.读取完配置类后发送ApplicationPreparedEvent5.在刷新上下文之后但在调用任何应用程序和命令行运行程序之前,将发送ApplicationStartedEvent6.紧随其后发送带有LivenessState.CORRECT的AvailabilityChangeEvent,以指示该应用程序被视为处于活动状态。
7.在调用任何应用程序和命令行运行程序之后,将发送ApplicationReadyEvent8.紧随其后发送ReadabilityState.ACCEPTING_TRAFFIC的AvailabilityChangeEvent,以指示应用程序已准备就绪,可以处理请求。
如果启动时发生异常,则发送ApplicationFailedEvent

2.2 详细步骤

2.2.1 调用SpringApplication.run()静态方法启动springboot应用

用过springboot的技术人员很显而易见的两者之间的差别就是视觉上很直观的:springboot有自己独立的启动类(独立程序),调用静态方法run():

@SpringBootApplication // 标记成Springboot的启动类
public class Application {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(SpringApplication.class,args);
    }

2.2.2 使用自定义SpringApplication进行启动

内部new了一个SpringApplication实例,并调用实例上的run()方法:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

分2步,创建SpringApplication实例和执行SpringApplication实例

2.2.2.1 创建SpringApplication

看下构造函数:

new SpringApplication(primarySources)

进一步调用重载的构造函数:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   // [1]将启动类放入primarySources 
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   // [2]根据classpath 下的类,推算当前web应用类型(webFlux, servlet)
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   // [3]就是去spring.factories 中去获取所有key:org.springframework.context.ApplicationContextInitializer
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   //[4]就是去spring.factories 中去获取所有key: org.springframework.context.ApplicationListener
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   // [5]根据main方法推算出mainApplicationClass 
   this.mainApplicationClass = deduceMainApplicationClass();
}

[3]和[4] 很重要,其实就是去jar和classpath中去查找spring.factories下的扩展点:
在这里插入图片描述

META-INF/spring.factories扩展点原理可参见 《自动配置原理 1 加载自动配置类》
【SpringBoot】扩展机制之Spring Factories

[3]处代码 此时会加载实现了 org.springframework.context.ApplicationContextInitializer接口的扩展点(该接口的实现类):
在这里插入图片描述
[4]处代码 此时会加载实现了org.springframework.context.ApplicationListener接口的扩展点(该接口的实现类):

在这里插入图片描述
总结:

  1. 获取启动类
  2. 获取web应用类型
  3. 读取了对外扩展的ApplicationContextInitializer 、ApplicationListener
  4. 根据main推算出所在的类

就是去初始化了一些信息

2.2.2.1 启动

启动springboot最核心的逻辑

public ConfigurableApplicationContext run(String... args) {
    // [1]用来记录当前springboot启动耗时
   StopWatch stopWatch = new StopWatch();
   // 就是记录了启动开始时间
   stopWatch.start();
   // 它是任何spring上下文的接口, 所以可以接收任何ApplicationContext实现
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   //[2] 开启了Headless模式:
   configureHeadlessProperty();
   // [3]去spring.factroies中读取了SpringApplicationRunListener 的组件,  就是用来发布事件或者运行监听器
   SpringApplicationRunListeners listeners = getRunListeners(args);
   //[3.1] 发布1.ApplicationStartingEvent事件,在运行开始时发送
   listeners.starting();
   try {
       // 根据命令行参数 实例化一个ApplicationArguments 
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //[4] 预初始化环境: 读取环境变量,读取配置文件信息(基于监听器)
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      // [5]忽略beaninfo的bean
      configureIgnoreBeanInfo(environment);
      // [6]打印Banner 横幅
      Banner printedBanner = printBanner(environment);
      // [7]根据webApplicationType创建Spring上下文  
      context = createApplicationContext();
      //[7.0] 创建一系列FailureAnalyzer
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      // [7.1]预初始化spring上下文
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      //[7.2] 加载spring ioc 容器  相当重要  
      //由于是使用AnnotationConfigServletWebServerApplicationContext 启动的spring容器所以springboot对它做了扩展:
      //加载自动配置类:invokeBeanFactoryPostProcessors ,  创建servlet容器onRefresh
      refreshContext(context);
      //[7.3]查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们。
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}
  • [3] 去spring.factroies中读取了SpringApplicationRunListener 的组件,对于具体的集合的元素,目前只有一个,即EventPublishingRunListener实例。

    SpringApplicationRunListeners其本质上就是一个事件发布者,它在SpringBoot应用启动的不同时间点发布不同应用事件类型(ApplicationEvent),如果有哪些事件监听者(ApplicationListener)对这些事件感兴趣,则可以接收并且处理。

    • [3.1] 循环调用starting()方法,发布ApplicationStartingEvent事件,时间点是在运行开始时。

      回顾 <2.1 SpringBoot 事件监听器发布顺序>章节提到的事件顺序,ApplicationStartingEvent正是第一个事件,后续还会有其他的事件

  • [4] 预初始化环境

  • [6]打印Banner 横幅,当然我们自己也可以定制

  • [7] 创建一个ApplicationContext,对应AnnotationConfigServletWebServerApplicationContext类型的实例

    • [7.0] 创建一系列FailureAnalyzer ,创建流程依然是通过SpringFactoriesLoader获取到所有实现FailureAnalyzer接口的class,然后在创建对应的实例。FailureAnalyzer用于分析故障并提供相关诊断信息。
    • [7.1] 预初始化spring上下文
    • [7.2] 调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序
    • [7.3] 查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们
2.2.2.1.1 [3]去spring.factroies中读取了SpringApplicationRunListener 的组件

看下SpringApplicationRunListeners对应一个集合,集合的内容均是SpringApplicationRunListener接口的子类,我们来看下该接口的源码:

public interface SpringApplicationRunListener {

    // 运行run方法时立即调用此方法,可以用户非常早期的初始化工作
    void starting();
    
    // Environment准备好后,并且ApplicationContext创建之前调用
    void environmentPrepared(ConfigurableEnvironment environment);

    // ApplicationContext创建好后立即调用
    void contextPrepared(ConfigurableApplicationContext context);

    // ApplicationContext加载完成,在refresh之前调用
    void contextLoaded(ConfigurableApplicationContext context);

    // 当run方法结束之前调用
    void finished(ConfigurableApplicationContext context, Throwable exception);

}

可以看到,该接口定义了多个方法,分别在不同的时间点被调用,由其子类实现,负责发送特定的事件


[3] 处代码对于具体的集合的元素,目前只有一个具体的子类,即EventPublishingRunListener, 作用是发送ApplicationStartingEvent事件,由覆写的starting()实现,目的是告别别人“SpringBoot应用要开始执行了"。

我们看子类EventPublishingRunListener的源码:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    //负责发送ApplicationStartingEvent事件
	@Override
	public void starting() {
		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
	}
2.2.2.1.2 [4] 预初始化环境

[4] 预初始化环境: 读取环境变量,读取配置文件信息(基于监听器),包括配置要使用的PropertySource以及Profile。

创建并配置当前应用将要使用的Environment,Environment用于描述应用程序当前的运行环境,其抽象了两个方面的内容:配置文件(profile)和属性(properties),不同的环境(eg:生产环境、预发布环境)可以使用不同的配置文件,而属性则可以从配置文件、环境变量、命令行参数等来源获取。因此,当Environment准备好后,在整个应用的任何时候,都可以从Environment中获取资源。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   //[1] 根据webApplicationType 创建Environment  创建就会读取: java环境变量和系统环境变量
   //如果已存在Environment  ,则直接返回
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   // [2]将命令行参数读取环境变量中
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   // [3]将@PropertieSource的配置信息 放在第一位, 因为读取配置文件@PropertieSource优先级是最低的
   ConfigurationPropertySources.attach(environment);
   // [4]发布了ApplicationEnvironmentPreparedEvent 的监听器  读取了全局配置文件
   listeners.environmentPrepared(environment);
   // [5]将所有spring.main 开头的配置信息绑定SpringApplication
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   //更新PropertySources
   ConfigurationPropertySources.attach(environment);
   return environment;
}
  • [1] 根据webApplicationType 创建Environment 创建就会读取: java环境变量和系统环境变量;如果已存在Environment ,则直接返回

    如果是web项目就创建StandardServletEnvironment,否则创建StandardEnvironment

  • [2] 将命令行参数读取环境变量中,这样,就可以在环境变量中引用到命令行参数了。

  • [3] 将@PropertieSource的配置信息 放在第一位, 因为读取配置文件@PropertieSource优先级是最低的

    @PropertieSource优先级是最低,放在代码最靠前,后面加载的参数会覆盖前面的,详细的顺序可以参见 Spring Boot 配置优先级顺序

  • [4] 调用SpringApplicationRunListener的environmentPrepared()方法,通知事件监听者:应用的Environment已经准备好。

    当然,这里仍然是SpringApplicationRunListener的接口的子类EventPublishingRunListener负责实现,看下源码:

    public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    @Override
    	public void environmentPrepared(ConfigurableEnvironment environment) {
    		this.initialMulticaster
    				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
    	}
    

    此时是第二个事件

  • [5] 将所有spring.main 开头的配置信息绑定到 SpringApplication上

2.2.2.1.3 [7.1]预初始化spring上下文

[7.1]预初始化spring上下文

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
      //[1] 将准备好的Environment设置给ApplicationContext
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   //[2] 拿到之前读取到所有ApplicationContextInitializer的组件, 循环调用initialize方法
   applyInitializers(context);
   //[3] 调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者:ApplicationContext已经准备完毕
   //对应的事件是ApplicationContextInitializedEvent
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   //[4] 获取当前spring上下文beanFactory (负责创建bean)
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   // [5]在Spring下 如果出现2个重名的bean, 则后读取到的会覆盖前面
   // 在SpringBoot 在这里设置了不允许覆盖, 当出现2个重名的bean 会抛出异常
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   // [6]设置当前spring容器是不是要将所有的bean设置为懒加载
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   //[7] 加载所有的类
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   // 读取主启动类 (因为后续要根据配置类解析配置的所有bean)
   load(context, sources.toArray(new Object[0]));
   //[8]读取完配置类后发送ApplicationPreparedEvent。
   listeners.contextLoaded(context);
}
  • [1] 将准备好的Environment设置给ApplicationContext

  • [2] 遍历调用所有的ApplicationContextInitializer的initialize()方法来对已经创建好的ApplicationContext进行进一步的处理

  • [3] 调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者:ApplicationContext已经准备完毕

    对应的事件是ApplicationContextInitializedEvent,是第三个事件

  • [7] 将所有的bean加载到容器中(这里所有bean是指带@SpringBootApplication的启动类,不是指配置@Controller或@Service这样的bean,后者是在下面的refreshContext()加载的)

  • [8] 调用SpringApplicationRunListener的contextLoaded()方法,通知所有的监听者:ApplicationContext已经装载完毕

    具体的事件对应ApplicationPreparedEvent,对应第四个事件

2.2.2.1.4 [7.2] refreshContext()

refreshContext(context)方法(初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作:

最终会调用父类AbstractApplicationContext的refresh() 方法:

refresh()方法是不是似曾相识?对的,spring的启动流程也是相似的逻辑,参见《02.Ioc容器加载过程-Bean的生命周期源码深度剖析–spring笔记》中的<1.1.3 refresh()>章节

	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			
			prepareRefresh();
			
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			
			prepareBeanFactory(beanFactory);
			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);
				//[1] 处理 BeanFactoryPostProcessor 接口,此处会去加载自动配置类
				invokeBeanFactoryPostProcessors(beanFactory);
				//[2] 处理 BeanPostProcessor 接口
				registerBeanPostProcessors(beanFactory);
				// 初始hauler message source ,即国际化
				initMessageSource();
				
				initApplicationEventMulticaster();
				
				onRefresh();
			
				registerListeners();
				
				finishBeanFactoryInitialization(beanFactory);
				
				finishRefresh();
			}
			....
		}
	}

[1] 很重要,处理 BeanFactoryPostProcessor 接口,此处会去加载自动配置类

最终会调用ConfigurationClassPostProcessor. processConfigBeanDefinitions(BeanDefinitionRegistry registry)方法:

注意执行的步骤:

1. String[] candidateNames = registry.getBeanDefinitionNames();
2. parser.parse(candidates);
3. this.reader.loadBeanDefinitions(configClasses);
4. while循环处理candidateNames集合中定义的类,加载它们

步骤示意图,黄色区域是idea debug的调用栈,执行顺序从下往上,从main函数开始,层层调用到refresh()方法:
在这里插入图片描述

ConfigurationClassPostProcessor用于解析处理各种注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。具体代码对应parser.parse(candidates),当处理@import注解的时候,就会调用<自动配置>这一小节中的EnableAutoConfigurationImportSelector.selectImports()来完成自动配置功能

2.2.2.1.4 总结

去除事件通知点后,整个流程如下:
在这里插入图片描述

  1. 初始化SpringApplication 从spring.factories 读取 listener ApplicationContextInitializer 。
  2. 运行run方法
  3. 读取 环境变量 配置信息…
  4. 创建springApplication上下文:ServletWebServerApplicationContext
  5. 预初始化上下文 : 读取启动类
  6. 调用refresh 加载ioc容器
    加载所有的自动配置类
    创建servlet容器
  7. 在这个过程中springboot会调用很多监听器对外进行

上图为SpringBoot启动结构图,我们发现启动流程主要分为三个部分:

  • 第一部分进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器
  • 第二部分实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块
  • 第三部分是自动化配置模块,该模块作为springboot自动配置核心,在后面的分析中会详细讨论。

参考:
《spring boot(二):启动原理解析》

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值