springboot 的启动流程(一)
1. main方法里直接调用了 SpringApplication.run(DemoApplication.class, args); 方法,并传入了这个当前类的class对象和参数(外部传入进来的)
2. 这里使用了我们传入的class对象创建了一个springapplication对象,然后执行run方法,传入我们给定的参数。
(1) 创建springapplication的时候主要的操作就是对其内部的属性进行了一些赋值。
比如为这个resourceloader赋值,尽管传过来的值为null
为这个primarysources赋值,就是我们main方法中传过来的class对象
为initializers属性赋值,这些类都继承 ApplicationContextInitializer,
主要是在初始化applicationcontext的时候可以做一些工作,比如注册配置的属性资源,指定**的profile等。
设置完初始化器之后开始设置监听器,比如在applicationcontext启动或者关闭的时候可以监听到,处理一些事件
最后这是主类
主要是根据线程栈里获取到方法名为 main 的方法所在的那个class,就断定出他是主类。
到目前为止,一个springapplication就创建完成了,主要是对齐属性进行了赋值,接下来就会执行他的run方法了。
3. 执行springapplication的run方法
(1) run方法主要是运行springapplication的,创建并刷新一个applicationcontext
stopwatch是用来记录一个任务启动或运行的时间,主要是包装的一个时间戳( System.currentTimeMillis() ),就是当前applicationcontext的开始启动时间,记录了下来,以及当前任务的名称。
ConfigurableApplicationContext, 说明这个applicationcontext是可以配置的
这个类继承了applicationcontext,可以对其设置一些属性,比如applicationlistener,environment,beanfactorypostprocessor(注意: 不是beanpostprocessor),shutdownhook等。
(对于异常报告 和 设置 headless属性就略过了,,没什么说的), 看一下 springapplicationrunlistener(不是springapplicationlistener)
这里只有一个listeners属性,,维护了listener的list,list中也只有一个listener,就是用来发布事件的。只要你写一个类实现ApplicationListener接口,,那么就会接收到通知。
listeners.starting(); 触发监听器,说明springapplication要开始启动了
先是对我们传进来的参数进行了封装,形成了一个 DefaultApplicationArguments 对象,然后使用我们的这个DefaultApplicationArguments,和springapplicationrunlistener对初始化并预处理一个environment。
这里配置一些要忽略的bean信息,也就是spring在ioc容器中不会创建和初始化他们。
紧接着打印banner
我们可以看到,控制台打印了。。。
说一下这个print方法
首先它判断了当前的bannermode,如果是off,,则不打印(我们可以通过配置来改变他的值),否则创建一个默认的resourceloader进行打印(我们刚开始第一步的时候说给springapplication的属性赋值也就是创建springapplication创建的时候这个属性值是null,并没有一开始都初始化了)
这里就开始创建了applicationcontext了
这里主要是根据 webApplicationType 的值来创建。这个值也是在创建applicationcontext的时候就赋值好了(在上面的图中可以看到),通过 deduceWebApplicationType() 方法赋值的。
主要是看你当前的classpath下有哪个类,spring就会推断出你当前的应用是什么类型的应用
最终我这个项目
也就是会实例化一个
至此,ConfigurableApplicationContext 创建完毕
exceptionreport也不说了,是这个样子
就是程序启动过程中哪里出错了都会有一些推断,或者分析,以便于定位原因(spring的功能还是很完善啊。。。)
创建applicationcontext完成之后就得对他进行一些初始化工作,,或者说预处理工作(代码给的是 prepare)
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
# 这里将之前创建的environment赋值给applicationcontext,environment主要记录一些属性文件,**哪个profile等信息
context.setEnvironment(environment);
# 这个下面说
postProcessApplicationContext(context);
# 这里主要是调用所有的 ApplicationContextInitializer 的 initialize方法。这些ApplicationContextInitializer 在创建springapplication的时候已经赋值好了,这里在applicationcontext初始化的时候拿来调用了
applyInitializers(context);
# listener 也是一样,在springapplication创建的时候赋值好,这里调用,触发其方法
listeners.contextPrepared(context);
# 日志打印的一些功能。。。
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
# 这里先往内部的beanfactory中注册一些必要的单例的bean,applicationArguments,printedBanner,既然这个时候注入了beanfactory中,,那么我们程序代码里也可以获取到(通过autowire注解等)
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
# 这个sources 就是我们的主类
load(context, sources.toArray(new Object[0]));
# 触发监听器,,applicationcontext加载完成了
listeners.contextLoaded(context);
}
说一下这个 postProcessApplicationContext
这里向beanfactor中注入了一个单例的bean ("org.springframework.context.annotation.internalConfigurationBeanNameGenerator")用来对bean生成其对应的name,通常是class的第一个首字母小写来作为他的名字。。
这里控制台打印了一些信息,显示了触发默认的profile,就是在applicationcontext的prepare阶段完成的。
作完了一些预处理工作,下面就开始执行refresh方法了。这里的东西以后再说,太多了。。。
这个方法执行之后,所有的单实例的,不是lazy加载的bean都已经创建并初始化好了。。我们自己写的程序也运行起来了(我这里用的undertow服务器)
这里会打印出程序运行后的日志信息
(我中间debug的时候有点长啊。。。。)
之后stopwatch.stop(),整个springcontext或者说springapplication就正式运行起来了。
这一步就是令监听器触发applicationcontext完成的方法。
可以说监听器在springapplication的生命周期的各个阶段都在发挥着作用。
callrunner就是你从命令行输入的参数或运行程序时传递的参数都会被传送到这里进行处理。。
再之后,,触发监听器的运行方法。。。表示applicationcontext正在运行中
最后返回到了这个最初的方法,启动结束,我们的项目运行起来了。。(走了一大遭)
最后补充一下
最后我们可以拿到我们的applicationcontext,来通过代码关闭它,这样应用程序就结束啦。