内容简介:介绍springboot的启动原理
相关链接:
背景:SpringBoot 2.4
一、启动文件
SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,创建springboot项目之后会生成xxxApplication.java文件,该文件就是启动文件
run方法就是创建个spring容器,然后创建个web容器(tomcat,jetty等)启动.
让我们看到run方法是如何实现的
又调用了另一个run方法
这一步就是创建了SpringApplication对象并且执行了run方法SpringApplication实例化
二、SpringApplication实例化
从上图SpringApplication实例化源代码中可以看出,在SpringApplication实例初始化的时候,它主要做这几件事事情:
(1)-根据classpath里面是否存在某个特征类ConfigurableWebApplicationContext来决定是否应该创建一个为Web应用使用的ApplicationContext类型。
(2)-使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的 ApplicationContextInitializer。
(3)-使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
(4)-推断并设置main方法的定义类。
下面来看下具体的代码。
1-环境判断的deduceWebEnvironment()方法:
这里主要是通过判断REACTIVE相关的字节码是否存在,如果不存在,则web环境即为SERVLET类型。这里设置好web环境类型,在后面会根据类型初始化对应环境。
2-加载文件getSpringFactoriesInstances方法:
getSpringFactoriesInstances方法会加载META-INF/spring.factories文件,spring.factories文件中的默认的实现类,此处涉及两个类ApplicationListeners和ApplicationContextInitializer。
(1) ApplicationListener可以监听某个事件event,通过实现这个接口,传入一个泛型事件,在run方法中就可以监听这个事件,从而做出一定的逻辑
(2)ApplicationContextInitializer是spring组件spring-context组件中的一个接口,主要是spring ioc容器刷新之前的一个回调接口,用于处理自定义逻辑。
二、SpringApplication.run方法
Spring Boot应用的整个启动流程都封装在SpringApplication.run方法中,本质上其实就是在spring的基础之上做了封装,做了大量的扩张。我们看下其源码
该方法中实现了如下几个关键步骤:
1.创建了应用的监听器SpringApplicationRunListeners并开始监听
2.加载SpringBoot配置环境(ConfigurableEnvironment),
3.根据是否是web项目,来创建不同的ApplicationContext容器
4.实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误,
5.准备容器prepareContext方法将listeners、environment、banner、applicationArguments等重要组件与上下文对象关联
6.刷新容器,refreshContext(context)方法(初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作。
7.刷新容器后的扩展接口
回顾整体流程,Springboot的启动,主要创建了配置环境(environment)、事件监听(listeners)、应用上下文(applicationContext),并基于以上条件,在容器中开始实例化我们需要的Bean,至此,通过SpringBoot启动的程序已经构造完成,接下来我们来探讨具体实现过程。
(一)获取并开启监听器
1-获取监听器getRunListeners方法:
这个方法就是去拿到所有的SpringApplicationRunListener实现类,用于SpringBoot事件发布的,启动的时候会先加载spring会加载所有jar包下的META-INF/spring.factories,然后缓存起来
1.1-其中用到了getSpringFactoriesInstances方法:
该方法是获取spring.factories对应的监听器,其在SpringApplication的构造方法中调用了两次,分别用来设置属性List
我们进去看一下getSpringFactoriesInstances方法
1.2-上面通过反射获取实例时会触发EventPublishingRunListener的构造函数:
1.3重点来看一下其中的addApplicationListener方法:
EventPublishingRunListener的构造方法中,构造了一个SimpleApplicationEventMulticaster对象,并将SpringApplication的listeners中的全部listener赋值到SimpleApplicationEventMulticaster对象的属性defaultRetriever(类型是ListenerRetriever)的applicationListeners集合中,继承关系为:
2-启动监听器starting方法
进入starting方法,可以看出它对监听器进行了遍历
2-1进去listener.starting方法,
获取的监听器为EventPublishingRunListener,从名字可以看出是启动事件发布监听器,主要用来发布启动事件。
从上图可以看出构建了一个ApplicationStartingEvent事件,并将其发布出去,其中调用了resolveDefaultEventType方法,该方法返回了一个封装了事件的默认类型(ApplicationStartingEvent)的ResolvableType对象。我们接着往下看,看看这个发布过程做了些什么。
2-2 multicastEvent方法
这里会根据事件类型ApplicationStartingEvent遍历getApplicationListeners(event, type)获取对应的监听器,在容器启动之后执行响应的动作,对每个listener进行invokeListener(listener, event)。
2-2-1getApplicationListeners方法
其作用是:返回与给定事件类型匹配的ApplicationListeners集合,非匹配的侦听器会被提前排除;允许根据缓存的匹配结果来返回。
getApplicationListeners方法主要以下3点:
缓存retrieverCache、retrieveApplicationListeners以及retrieveApplicationListeners中调用的supportsEvent方法。流程是这样的:
(1)缓存中是否有匹配的结果,有则返回
(2)若缓存中没有匹配的结果,则从this.defaultRetriever.applicationListeners中过滤,这个this表示的EventPublishingRunListener对象的属性initialMulticaster(即SimpleApplicationEventMulticaster对象,而defaultRetriever.applicationListeners的值也是在EventPublishingRunListener构造方法中初始化的)
(3)过滤过程,遍历defaultRetriever.applicationListeners集合,从中找出ApplicationStartingEvent匹配的listener,具体的匹配规则需要看各个listener的supportsEventType方法(有两个重载的方法)
(4)将过滤的结果缓存到retrieverCache
(5)将过滤出的结果返回回去
过滤出的listener对象有以下几种
2-2-2 invokeListener方法
其作用是:使用给定的事件调用给定的监听器
2-3 onApplicationEvent方法
getApplicationListeners方法过滤出的监听器都会被调用,过滤出来的监听器包括LoggingApplicationListener、LiquibaseServiceLocatorApplicationListener、BackgroundPreinitializer、EnableEncryptablePropertiesBeanFactoryPostProcessor、DelegatingApplicationListener、五种类型的对象。这五个对象的onApplicationEvent都会被调用。根据发布的事件类型从上述监听器中选择对应的监听器进行事件发布,这里选了一个 springBoot 的日志监听器来进行讲解,核心代码如下:
包含以下五种情况
(1)在springboot启动的时候
(2)springboot的Environment环境准备完成的时候
(3)在springboot容器的环境设置完成以后
(4)容器关闭的时候
(5)容器启动失败的时候
(二)环境准备prepareEnvironment方法
2-1 getOrCreateEnvironment方法
前面已经提到,environment已经被设置了servlet类型,所以这里创建的是环境对象是StandardServletEnvironment。
在返回return new StandardServletEnvironment();对象的时候,会完成一系列初始化动作,主要就是将运行机器的系统变量和环境变量,加入到其父类AbstractEnvironment定义的对象MutablePropertySources中,MutablePropertySources对象中定义了一个属性集合:private final List
执行到这里,系统变量和环境变量已经被载入到配置文件的集合中,接下来就行解析项目中的配置文件。
2-2 listeners.environmentPrepared方法
进入该方法
这里是第二次发布事件:系统环境初始化完成的事件。发布事件的流程上面已经讲过了,这里不在赘述。
(三)创建容器createApplicationContext方法
进入该方法
上面可以看出,这里创建容器的类型 还是根据webApplicationType进行判断的,前文已经讲述了该变量如何赋值的过程。因为该类型为SERVLET类型,所以该容器的名称为AnnotationConfigServletWebServerApplicationContext
依赖关系如下图所示。
(四)准备容器prepareContext
这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础.
4-1postProcessApplicationContext方法
容器的post处理,子类可以根据需要申请额外处理。
这里默认不执行任何逻辑,因为beanNameGenerator和resourceLoader默认为空。之所以这样做,是springBoot留给我们的扩展处理方式,类似于这样的扩展,spring中也有很多。
4-2applyInitializers方法
将ApplicationContextInitializer应用到上下文
在 applyInitializers 中遍历调用每一个被加载的 ApplicationContextInitializer 的 initialize(context); 方法,并将 ConfigurableApplicationContext 的实例传递给 initialize 方法。
4-3 listeners.contextPrepared(context)
通知监听器,上下文准备好了,调用EventPublishingRunListener的contextPrepared,发现其是空实现,也就是相当于啥事也没做。
4-4 load加载启动类
这里会将我们的启动类加载spring容器beanDefinitionMap中,为后续springBoot 自动化配置奠定基础,springBoot为我们提供的各种注解配置也与此有关。
需要注意的是,springBoot2会优先选择groovy加载方式,找不到再选用java方式。或许groovy动态加载class文件的性能更胜一筹
4-5 listeners.contextLoaded(context)
通知监听器,容器已准备就绪
(五)刷新容器refreshContext(context)
执行到这里,springBoot相关的处理工作已经结束,接下的工作就交给了spring。
实际上Tomcat的启动也是在refresh流程中,这个方法其中一步是调用了onRefresh方法,在Spring中这是一个没有实现的模板方法,而SpringBoot就通过这个方法完成了Tomcat的启动:
这里首先拿到TomcatServletWebServerFactory对象,通过该对象再去创建和启动Tomcat:
一路跟进去,refresh方法在spring整个源码体系中举足轻重,是实现 ioc 和 aop的关键。上述流程,不是一篇博文能够展示清楚的,所以这里暂时不做展开。后续会有详细的介绍。
(六)刷新容器后的扩展接口refreshContext
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
至此springBoot2启动流程到这里就结束了,引用一张流程图。
上图为SpringBoot启动结构图,我们发现启动流程主要分为三个部分,第一部分进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器,第二部分实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块,第三部分是自动化配置模块,该模块作为springboot自动配置核心,