总结:Springboot启动流程

一、总结

我基于需求提出者或者提问者的角度去描述Spring boot的启动流程:

1、Springboot启动过程需要做什么?

  • 读取我们定义的配置文件。
    1. 如application-pro.properties,application.properties,logback-spring.xml等,因为我们希望程序按照我们的配置去执行。
    2. 换个说法,其实就是加载运行环境,也就是代码ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);做的事情
  • 创建对象:包括我们自定义的类、引入的依赖包的类
    1. 比如我们的controller,Service注解的类,Component注解的类
    2. 比如依赖的RocketMQ组件,代码中使用的比如RocketMQTemplate对象,并不是我们自己创建的,而是Springboot帮创建的。
    3. 也就是代码:refreshContext(context);主要做的事情。
  • 提供Web服务
    1. 我们提供Web服务需要一个Web服务器,一般主流的入Tomcat,Jetty等。Springboot内置的Tomcat是如何启动并提供服务的?其实就是代码context = createApplicationContext();和refreshContext(context);做的事情。

  以上几点是我们一般用户比较容易想到让Springboot帮我们做好的事情。

但是还有一些潜在的需求,比如:

1、使用过程中报错怎么办?能否自助排障?

Springboot作为一个需求开发者,当开发的系统出问题,总得要排查吧?总不能让使用者碰到错误就让开发人员去排查吧?所以Springboot为了让用户能够自助排查问题,把整个Springboot启动过程中的事件监控起来,即SpringApplicationRunListeners干的事情,它把执行过程以及执行失败的日志打印出来告知使用者。

二、启动流程描述

1、启动流程总览

启动流程主要分为三个部分:

第一部分:进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器

第二部分:实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块

第三部分:是自动化配置模块,该模块作为springboot自动配置核心,在后面的分析中会详细讨论

2、关于SpringApplication

SpringBoot的启动主要是通过实例化SpringApplication类,并调用run方法来启动的。

3、实例化SpringApplication时做了什么?

SpringApplication的构造方法做了几件事情:

  • 推断WebApplicationType,主要思想就是在当前的classpath下搜索特定的类
  • 搜索META-INF\spring.factories文件配置的ApplicationContextInitializer的实现类
  • 搜索META-INF\spring.factories文件配置的ApplicationListener的实现类推断MainApplication的Class

4、SpringApplication的run方法做了什么?

  • 创建一个StopWatch并执行start方法,这个类主要记录任务的执行时间
  • 配置Headless属性,Headless模式是在缺少显示屏、键盘或者鼠标时候的系统配置
  • 在文件META-INF\spring.factories中获取SpringApplicationRunListener接口的实现类EventPublishingRunListener,主要发布SpringApplicationEvent。说白了,SpringApplicationRunListener作用就是监听SpringApplication的run方法的运行情况的。具体参考:SpringApplicationRunListener 是干啥的?
  • 把输入参数转成DefaultApplicationArguments类
  • 创建Environment并设置比如环境信息,系统属性,输入参数和profile信息
  • 打印Banner信息
  • 创建Application的上下文,根据WebApplicationType来创建Context类,如果非web项目则创建AnnotationConfigApplicationContext,在构造方法中初始化AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner
  • 在文件META-INF\spring.factories中获取SpringBootExceptionReporter接口的实现类FailureAnalyzers
  • 准备application的上下文
    1. 初始化ApplicationContextInitializer
    2. 执行Initializer的contextPrepared方法,发布ApplicationContextInitializedEvent事件
    3. 如果延迟加载,在上下文添加处理器LazyInitializationBeanFactoryPostProcessor
    4. 执行加载方法,BeanDefinitionLoader.load方法,主要初始化了AnnotatedGenericBeanDefinition
    5. 执行Initializer的contextLoaded方法,发布ApplicationContextInitializedEvent事件
  • 刷新上下文(加载tomcat容器),在这里真正加载bean到容器中。如果是web容器,会在onRefresh方法中创建一个Server并启动。
  • 再刷新上下文
  • 发布应用已经启动事件
  • 发布应用启动完成事件。

四、核心启动源码之initialize

1、整体代码

如下会new SpringApplication对象,这个操作会调用初始化方法,

@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
	//sources即我们的启动文件,如Starter.java
	if (sources != null && sources.length > 0) {
		this.sources.addAll(Arrays.asList(sources));
	}
	
	//确定是否是webEnvironment,后续会根据是否是web环境进行一些操作,如上下文初始化实例的选择(createApplicationContext)
	this.webEnvironment = deduceWebEnvironment();
	
	//上下文初始化:使用内部工具类SpringFactoriesLoader从META-INF/spring.factories加载加载所有ApplicationContextInitializer实现类并实例化,
	//并将这些实例设置到SpringApplication的List> initializers中
	//初始化实例包含:
	setInitializers((Collection) getSpringFactoriesInstances(
			ApplicationContextInitializer.class));
			
	//监听实例初始化:从META-INF/spring.factories加载实现了ApplicationListener接口的类并生成实例对象,
	//并将这些实例设置到SpringApplication的private List> listeners中
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	
	//获取main函数所在的类
	this.mainApplicationClass = deduceMainApplicationClass();
}

2、springboot - web环境的推断

SpringApplication会尝试帮你创建正确的ApplicationContext,默认情况下会使用AnnotationConfigApplicationContext或者 AnnotationConfigEmbeddedWebApplicationContext,这取决于你开发的是否是web环境。判断Web environment算法是非常简单的,直接基于某些类的存在与否。

如下:直接判断类路径下是否存在"javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"两个类即可。

总结:Springboot启动流程_第1张图片

3、上下文初始化

总结:Springboot启动流程_第2张图片

上下文实例详解:

//缺省 Web SpringApplication 应用会有以下 6 个 ApplicationContextInitializer: 
//1.初始化任务委托给Environment指定的初始化器,
//相当于给外界提供了一个添加自定义ApplicationContextInitializer的入口
//委托给Environment属性context.initializer.classes下指定的ApplicationContextInitializer类,
DelegatingApplicationContextInitializer

//2.设置Spring ApplicationContext ID
//这些环境变量会被用来产生Spring ApplicationContext ID : 
// spring.application.name,vcap.application.name,spring.config.name
// 如果没有找到以上属性设置,ID使用 application
ContextIdApplicationContextInitializer

//3.用于报告一般配置错误,
//添加BeanFactoryPostProcessor : ConfigurationWarningsPostProcessor
ConfigurationWarningsApplicationContextInitializer 

//4.添加 ApplicationListener
ServerPortInfoApplicationContextInitializer

//5.在ConfigurationClassPostProcessor和Spring boot之间增加一个共享的CachingMetadataReaderFactory
//添加BeanFactoryPostProcessor : CachingMetadataReaderFactoryPostProcessor
SharedMetadataReaderFactoryContextInitializer

//6.将ConditionEvaluationReport写到log,在DEBUG级别下输出
//添加AutoConfigurationReportListener
AutoConfigurationReportLoggingInitializer

4、监听器初始化

总结:Springboot启动流程_第3张图片

五、核心启动源码之run方法

1、整体代码

public ConfigurableApplicationContext run(String... args) {
		//创建计时器
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		// 声明 IOC 容器
		ConfigurableApplicationContext context = null;
		FailureAnalyzers analyzers = null;
		
		//作用:即使没有检测到显示器,也允许其启动。对于服务器来说,是不需要显示器的,所以要这样设置
		configureHeadlessProperty();
		
		//准备运行时监听器EventPublishingRunListener,方式为从类路径下找到 META/INF/Spring.factories 获取 SpringApplicationRunListeners
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			
			// 封装命令行参数,也就是在命令行下启动应用带的参数,如--server.port=9000 
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
					
			// 准备环境,1、加载外部化配置的资源到environment;2、触发ApplicationEnvironmentPreparedEvent事件,
			//创建环境完成后回调 SpringApplicationRunListeners#environmentPrepared()方法,表示环境准备完成 
			// 环境准备的主要工作就是 将系统属性配置及用户定义的属性配置 加载进来
			ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
			
			// 打印banner图。banner即下面这坨东西,可以设置关掉打印,也可以修改
						.   ____          _            __ _ _
			 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
			( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
			 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
			  '  |____| .__|_| |_|_| |_\__, | / / / /
			 =========|_|==============|___/=/_/_/_/
			 :: Spring Boot ::        (v1.5.6.RELEASE)
			 
			Banner printedBanner = printBanner(environment);
			
			//创建应用上下文,决定创建web的ioc还是普通的ioc
			context = createApplicationContext();
			
			//容器启动失败分析,如端口被占用等
			analyzers = new FailureAnalyzers(context);
			
			//做context的准备工作,如把相关需要创建bean的类加载到上下文中
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			
			//刷新上下文,
            //很重要的就包含对上面被加载的bean进行实例化
            //另外,@EnableAutoConfiguration相关配置也在这里将需要装配的bean给装配好
			refreshContext(context);
			
			//刷新Context容器之后处理
			afterRefresh(context, applicationArguments);

            //通过EventPublishingRunListener发布finished事件
			listeners.finished(context, null);

			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			return context;
		}
		catch (Throwable ex) {
			handleRunFailure(context, listeners, analyzers, ex);
			throw new IllegalStateException(ex);
		}
	}

2、SpringApplicationRunListeners

SpringApplicationRunListener 的作用是在SpringApplication 的各个启动过程中,监听各个阶段的变化,并将每个阶段封装成事件(ApplicationEvent),发布出去。让其他监听这些事件的监听器能探测到,并调用到对应的处理方法。

1. SpringApplicationRunListener 是一个接口,它的主要实现类是 EventPublishingRunListener 类。主要作用是监听 SpringApplication 启动的各个阶段,并封装成对应的 ApplicationEvent ,并把它发布出去。

2. 这里运用到一个观察者模式,不懂的小朋友可以多百度一下。

3. 我们可以定义自己的SpringApplicationRunListener, 实现这个接口,然后再新建一个 META-INF/spring.factorie 文件,注册监听即可。

3、SpringApplicationRunListeners与ApplicationListener的区别

SpringApplicationRunListeners负责在SpringBoot启动的不同阶段, 广播出不同的消息, 传递给ApplicationListener监听器实现类。

SpringApplicationRunListeners从Java监听器的角度看,并不是一个监听器,只是个普通的Java类而已,可以去看这个类的定义。

而ApplicationListener是传统意义上的Java监听器,如实现类DelegatingApplicationListener, 实现了ApplicationListener,ApplicationListener继承了EventListener。

4、ApplicationContextInitializer

用来在对ApplicationContext进行refresh操作之前对Application context进行一些初始化操作。

主要用来在ConfigurableApplicationContext#refresh()之前对ConfigurableApplicationContext进行一些初始化的操作。一般来说主要做一些程序化的操作,比如注册配置源,根据配置激活某个profile等

5、ApplicationListener

基于观察者模式的Application的事件监听器。将ApplicationListener注册到ApplicationContext中,当有对应事件发生时,监听器会被调用。

ApplicationContextInitializerApplicationListener的加载是在类SpringApplication的构造函数中完成的,具体都是通过调用函数getSpringFactoriesInstances(Class type)来完成的。

在spring boot中默认配置的ApplicationListener还挺多的,我们就摘抄几个比较重要的分析一下。

ConfigFileApplicationListener

ConfigFileApplicationListener是一个非常重要的监听器,除了是一个监听器之外,它实现了接口EnvironmentPostProcessor。在监听到事件时,会从指定的文件加载配置并配置application context。

可以看到继承实现了接口EnvironmentPostProcessor的方法postProcessEnvironment,负责对SpringApplication的Environment进行处理。具体实现上可以看到创建了一个Loader,这个Loader会负责去javadoc描述得地方去查找文件并加载配置。

public class ConfigFileApplicationListener
		implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
    private void onApplicationEnvironmentPreparedEvent(
    		ApplicationEnvironmentPreparedEvent event) {
    	List postProcessors = loadPostProcessors();
    	postProcessors.add(this);
    	AnnotationAwareOrderComparator.sort(postProcessors);
    	for (EnvironmentPostProcessor postProcessor : postProcessors) {
    		postProcessor.postProcessEnvironment(event.getEnvironment(),
    				event.getSpringApplication());
    	}
    }
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
    		SpringApplication application) {
    	addPropertySources(environment, application.getResourceLoader());
    }
    protected void addPropertySources(ConfigurableEnvironment environment,
    		ResourceLoader resourceLoader) {
    	RandomValuePropertySource.addToEnvironment(environment);
    	new Loader(environment, resourceLoader).load();
    }
}

重点看一下方法onApplicationEnvironmentPreparedEvent。在监听到ApplicationEnvironmentPreparedEvent事件之后,除了将自己加入到List之外,还会调用函数loadPostProcessors去加载在文件META-INF/spring.factories配置的EnvironmentPostProcessor,最后执行所有的postProcessEnvironment方法。


六、EnableAutoConfiguration

@EnableAutoConfiguration结合spring.factories实现了对外部依赖包的扫描。

具体参考我的另一篇博客:总结:Spring boot之@EnableAutoConfiguration

参考:

总结:Spring Boot 之spring.factories

Spring Boot创建Beans的过程分析

Springboot Feign整合源码解析

Spring Boot 中 @EnableXXX 注解的驱动逻辑

Spring Boot启动过程分析

SpringApplicationRunListener 是干啥的?

spring-boot-2.0.3不一样系列之源码篇 - run方法(三)之createApplicationContext,绝对有值得你看的地方

SpringApplication 的初始化过程分析 : initialize()

SpringBoot启动流程解析

你可能感兴趣的:(Java,spring,boot,java,spring)