前言
之前用SpringBoot的时候就很好奇,SpringBoot究竟是如何启动的呢?今天花了点时间稍微研究了一下,认真看下到底是如何实现的。
1 |
|
可以看到就是这个类的main方法启动了整个框架,主题就是一个SpringApplication类的一个静态的run方法,把当前类作为类对象进行了传入,并且还把参数也顺手传递了进去。就这么简单。所以重要的其实就是SpringApplication这个类。
除了这个类之外,还不要忽略了@SpringBootApplication
这个注解,正是注解+代码结合在一起才让spring真正的启动了起来。
@SpringBootApplication
其实这个注解本质上就是@Configuration
+ @EnableAutoConfiguration
+ @ComponentScan
,也就是我们理解了这三个注解基本就理解了启动过程的一半了。
@Configuration
这个注解其实就相当于声明这个类是一个配置类,相当于之前的spring的xml配置文件——即IoC容器的配置类。如果你理解spring的配置文件,这个也不难理解。
@EnableAutoConfiguration
简单来说,底层其实靠的就是@Import
支持,而Import的本质就是引入配置文件。所以就是把所有符合自动配置的bean给加载到IoC容器中。
具体来说,其实就是把一个AutoConfigurationImportSelector放到了容器中,它的职能就是扫描所有的带有@Configuration的类,并且把它们加入到容器中。
@ComponentScan
在spring中,其实就是自动扫描并且把相对应的bean加入到容器中。
@Conditional
虽然这个注解并不算在启动三大将的注解中,但是通过它和它的兄弟们,我们可以按需加载一些配置,所以姑且拿出来讲一讲吧。
SpringApplication
首先站在全局的角度来高度浓缩一下启动流程:
- 收集好各种条件和回调的接口
- 创建并且准备好Environment
- 创建并且初始化ApplicationContext,加载配置等
- refresh一下,ApplicationContext启动完成,结束!
然后再根据实际的代码来跟踪一下,发现其实调用的是静态方法是这样子的:
1 | public static ConfigurableApplicationContext run(Object[] sources, String[] args) { |
首先使用了该类自己的构造方法创建了一个对象,并且调用了对象的另外一个run方法,所以如果硬要说的话,必须先构造出SpringApplication,然后在调用它的run方法。
构造方法
1 | public SpringApplication(Object... sources) { |
刨去一些初始化的东西,真正重要的其实是最后一句this.initialize(sources);
,然后继续跟踪:
1 | private void initialize(Object[] sources) { |
首先把重心放在if判断里面。可以看见是给类的成员变量source(一个hashset)增加了一个成员,即MainApplication.class。
然后判断一下当前是否是web环境,通过一个deduceWebEnvironment的方法来判断。这个方法判断逻辑也很简单,就是判断类加载器里面是否有特定的类。这个就不展开细讲了。
接下来就是设置好初始化器,并且做一些初始化的工作。
然后是一些应用程序事件监听器。
ApplicationContextInitializer
从这个名字里也不难得出结论,去spring.factories文件中读取指定的类,并且将它们进行实例初始化,并且设置成监听器,我跟踪了一下,有下面这些会被加载进来:
一共有六个,但是打开文件可以看到:
最后那两个怎么来的呢?
ApplicationListener
和上面的一样的,通过一样的方法,找到listener,并且进行实例初始化。
一共有10个,然后去spring.factories去看看:
和上面的一样,文件里只有9个,但是被实例化的却有10个。对比这两个我们不难发现,其实有一个autoconfigure包下面的也加了进来。这里我们先暂时不深究为什么会多出来。
这里要多说明一下,ApplicationListener就是用来监听ApplicationEvent的。
构造完成之后,执行之前
现在我们已经了解了,在构造函数中使用了initialize,而在这个方法中最主要的就是两件事:实例化一堆Initializer和一堆Listener。
Initializer比较好理解,就是完成初始化工作的。而Listener中有一个比较重要的,我特地把它的源代码拿出来:
1 | public interface SpringApplicationRunListener { |
其实看到这个接口的名字也知道了,就是用来监听SpringApplication的Run方法的。我目前只看到有一个EventPublishingRunListener的实现类。这个实现类用来在SpringBoot启动的不同阶段发布不同的事件,如果有哪个ApplicationListener对这些事件感兴趣,则可以接受并且处理。
更加具体的来说,它的实现类,会构造出一个一个的ApplicationEvent,并且把这个事件给广播出去。而我们之前说那些ApplicationListener就会有相应。
OK,到了这一步,算是真真正正的把这个SpringApplication给构造出来了。
执行run方法
这一部分的逻辑见下:
1 | public ConfigurableApplicationContext run(String... args) { |
总得来说,确实比较长噢,那么稍微来整理一下。
- new了一个stopWatch,并且启动了它。这个东西就是一个计时器,通过获取系统时间来实现的。
- 第7行,获取了SpringApplicationRunListeners(注意这个
s
),然后启动了它。实现的逻辑就是对于内部所有的SpringApplicationRunListener都调用其starting方法(当然现在来说只有一个) - 第12行,构造了应用的参数,并且封装了一下。
- 第13行,准备好Springboot所需环境
- 第16行,在这一步,终于把ApplicationContext给构造了出来。
- 第19行执行了refresh操作。
- 第21行,停止了计时器。
- 第35行,返回了IoC容器。
最最重要的逻辑应该就是创建IoC容器的部分了吧,也就是createApplicationContext方法的实现:
1 | protected ConfigurableApplicationContext createApplicationContext() { |
可以看到很明显通过策略模式来选择创建了三种ApplicationContext,我们一般都会进入到SERVLET这个选项中,然后会为我们创建AnnotationConfigServletWebServerApplicationContext。
创建完成之后,马上执行了refresh操作,这个也是很值得回味的过程。
1 | public void refresh() throws BeansException, IllegalStateException { |
这块的内容比较多,所以只是简单概括一下:首先可以看到是通过synchronized来进行同步的,然后就是BeanFactory的设置,BeanFactoryPostProcessor接口的执行、BeanPostProcessor接口的执行等等等。
然后是具体的逻辑部分。
####prepareRefresh
显然是在真正做refresh之前需要完成的事。
1 | // 记录下时间,并且修改状态为启动状态 |
prepareBeanFactory
这个方法主要做了这些事情:
- 告知内部的bean factory使用classloader,设置bean表达式的解析器,设置PropertyEditor(用来把2018/08/08转成date的东西)
- 为bean factory增加了一个ApplicationContextAwareProcessor,同时忽略了5个已经被自动注入的Aware接口。
- 实现一些资源加载、国际化等bean。
- 又加了一个ApplicationListenerDetector作为BeanPostProcessor,这个估计是新加的。
- 注册一些别的bean到容器中。
postProcessBeanFactory
这里根据不同的容器会进行不同的操作。
invokeBeanFactoryPostProcessors
以下暂时省略。
总结
SpringBoot的启动流程其实只要抓住两个流程,分别是SpringApplication对象的建立和run方法的执行。
在对象建立的时候:
- 判断是否是web环境
- 从spring.fatories中找到所有的Initializer,并且设置到一个List中(仅仅如此)
- 从spring.fatories中找到所有的listener,并且设置到另外一个List中(仅此而已)
在run方法执行的时候:
- 构造一个stopWatch用来记录时间。
- 找到SpringApplicationRunListener并且封装进SpringApplicationRunListeners
- 初始化容器并且完成容器的一些工作。