说明:spring boot版本 2.0.6.RELEASE
思绪
首先,大家认识spring boot是从@SpringBootApplication注解和org.springframework.boot.SpringApplication.run(Class<?>, String...)开始的,那么我们就从这两个方向入手一探究竟。
@SpringBootApplication注解
先来看下@SpringBootApplication的申明,如下图:
核心也就是@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan这三个注解的组合。
@SpringBootConfiguration如下,即标明被@SpringBootApplication注解标注的类本身也是spring的java config类。
@ComponentScan,配置自动扫描功能,等同xml配置的<context:component-scan>。可以指定basePackages或者basePackageClasses标明扫描的根路径,如果不设置的话默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,也就是@SpringBootApplication所在的类,所以一般spring boot项目的入口类会放在顶层目录中,这样就可以自动扫描到项目中所有的spring配置类,该注解的处理类为org.springframework.context.annotation.ComponentScanAnnotationParser.parse(AnnotationAttributes, String),从ApplicationContext中refresh的invokeBeanFactoryPostProcessors调用过来的,调用栈如下图所示:
@EnableAutoConfiguration,自动配置类,是简化spring配置的核心,spring一贯的风格凡是enable开头的注解都会配备一个@Import注解来引入一个ImportSelector。这里的@Import如下图所示:
扫盲:@Import注解作用是将values配置的class加入springIOC容器中:
如果是@Configuration的配置类,则将对应的java config产生的bean纳入spring管理;
如果是普通java类,则将该类实例化并纳入spring管理;
重点看下org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.selectImports(AnnotationMetadata)方法,返回一个string数组,内容为需要加载的类限定名。
核心是通过spring的FactoriesLoader机制加载以EnableAutoConfiguration注解全限定类名为key的class。
核心在org.springframework.core.io.support.SpringFactoriesLoader.loadSpringFactories(ClassLoader)方法中,如下图所示,其实就是在classpath下查找所有
![](https://img2018.cnblogs.com/blog/1313949/201904/1313949-20190410163007605-669160805.png)
下图就是其中一个示例:
spring.factories相关内容如下:
以直观的web容器自动配置为例:
其中的ServletWebServerFactoryAutoConfiguration和ReactiveWebServerFactoryAutoConfiguration即为根据ConditionalOnWebApplication应用类型来加载对应的webServerFactory,classpath下的自动配置webServer:
备注:ServletWebServer和ReactiveWebServer是两个路子,根据WebApplicationType走不同的路。
拿ReactiveWebServerFactoryAutoConfiguration来说,如下图所示,@Import了ConditionalOnWebApplication.Type.REACTIVE应用类型可能用到的WebServerFactory。
内部再根据对应条件创建对应的WebServerFactory。
最终org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.getWebServerFactory()以及
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getWebServerFactory()从BeanFactory获取对应的WebServerFactory
串起来的话是在org.springframework.context.support.AbstractApplicationContext.refresh()中的onRefresh()[子类]的createWebServer创建server,finishRefresh()[子类]启动server。
好了,到这里spring boot的@SpringBootApplication注解就分析完了。
SpringApplication.run分析
方法入口先创建SpringApplication对象,然后调用实例run方法,如下图。
先看看构造方法都做了些什么:
1. 推断webApplicationType
2. 通过SpringFactoriesLoader机制获取并创建ApplicationContextInitializer和ApplicationListener
3. 推断应用主class(mainApplicationClass)
这些是用于扩展用的,在整个启动过程中插入对应的扩展点,具体逻辑要看实现这些接口的具体类里面的逻辑。
我们先来看主线流程,分析源码要先把主线流程逻辑拉通,再看各个点的逻辑,由面及点,这样才不会让自己陷入其中,不明所以。
如下图所示,主线其实就是创建了一个context,prepareContext以及refresh该context。
创建context
根据之前构造方法中推动出的应用类型创建对应的context,如下图所示。
我们拿reactive类型来说,对应的context为org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
继承关系如下:
创建没什么,就这么多,重点是下面讲的prepareContext
prepareContext
里面重点是加载beanDefinitions,而原来spring框架里面是在org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory()创建完beanFactory后loadBeanDefinitions,详见下面refreshContext的分析。
refreshContext
最终调用的是org.springframework.context.support.AbstractApplicationContext.refresh()这个方法,了解spring的人应该很清楚这个方法,我之前的spring启动过程源码分析文章也讲得很清楚了。方法内容如下:
重点看下obtainFreshBeanFactory方法,如下图所示:
里面重点是refreshBeanFactory方法,我们先看下继承关系:
而spring原有的context继承自上面那个类,如下图所示:
所以refreshBeanFactory在以前spring本身的代码中,调用的是org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory()方法,如下图所示,先销毁再创建新的beanfactory,同时加载beanDefinitions,当然,第一创建beanFactory也是在这里。其实从类名很清楚就可以看出,这个基类是支持动态刷新context的:
而spring boot继承的基类只是setSerializationId,如下图:
spring boot创建beanFactory是在org.springframework.context.support.GenericApplicationContext.GenericApplicationContext()构造方法中。
至此,spring boot 创建context已经完成,在子类org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.onRefresh()方法中创建webServer,
在finishRefresh中启动webServer:
子类重写的这两个方法在父类org.springframework.context.support.AbstractApplicationContext.refresh()方法中调用的,有没有get到?这下都串起来了吧。
完结