文章目录
源码版本
- 2.3.x
new Application
- 首先我们的SpringBoot的启动都是通过SpringApplication的静态方法run开始的,所以我们从run开始看起
- 进入run方法发现里边调用了类内部的一个run方法
- 再进去以后发现new了一个SpringApplication然后调用了对象的run
- 先看SpringApplication的构造方法做了什么事情
- 首先primarySources指向了primarySources也就是我们的启动类
- 然后webApplicationType获取启动类型
- 将getSpringFactoriesInstances(ApplicationContextInitializer.class)和getSpringFactoriesInstances(ApplicationListener.class)设置到自己的属性里
- 再将mainApplicationClass指向启动类
- 我们重点看getSpringFactoriesInstances()方法,两个语句执行了同一个方法,所以我们只需要看其中一个就可以,然后执行完以后将返回值设置到了initializers和listeners中
getSpringFactoriesInstances()
- 进来以后发现主要有两个方法SpringFactoriesLoader.loadFactoryNames()和createSpringFactoriesInstances(),我们分开看
SpringFactoriesLoader.loadFactoryNames()
- debug可以看到第一次进来cache是没有缓存的,所以会继续向下执行
- 然后classLoader是不为空的,所以URL地址是META-INF/spring.factories,在while循环中就是从这个URL加载配置进去的类最后封装到result这个map中然后再放入cache,也就是SPI机制,自动装配和自定义starter都是通过这个实现的。其中有一个很重要的key是EnableAutoConfiguration,value为所有通过SPI机制加载出来的类
- 刚才两个方法的参数分别是ApplicationContextInitializer.class和ApplicationListener.class,所以就是从META-INF/spring/factories中加载这两个接口的实现,也就是为什么实现这两个接口必须配置在META-INF/spring/factories中才会被SpringBoot所加载
- ApplicationListener接口虽然在这里会以加载文件的方式加载进来,但如果不在文件中配置的话这个类按理说是不会生效的,但是加上@Component注解这个类还是生效了,这块暂时还不太明白
createSpringFactoriesInstances()
- 这个方法主要是通过反射实例化需要加载的类然后返回
- 然后我们知道了这个new SpringApplication做了什么事情以后回到实例完以后调用的run方法中去
run
- 这个方法是最重要的方法
- 实例化时间类计算启动时间
- new StopWatch()
- 获取监听器并在有事件时进行调用
- getRunListeners()
- 实例化参数类
- new DefaultAppicationArguments()
- 准备环境
- prepareEnvironment()
- 打印标志
- printBanner()
- 创建容器
- createApplicationContext()
- 准备工作
- prepareContext()
- 启动容器
- refreshContext()
- 启动之后
- afterRefresh()
- 调用扩展点
- callRunners()
getRunListeners()
- 点进来这个方法发现主要是new了一个SpringApplicationRunListeners的对象,然后通过文件加载所有的SpringApplicationRunListener作为参数传入构造方法,所以SpringApplicationRunListener接口也是只能通过文件方式才可以被加载
- 进来以后发现这个类有一个List<SpringApplicationRunListener>类型的参数,然后把所有的监听器放入到这个集合中
- 所以这里使用了监听器模式,被监听的对象改变然后监视者通知所有的监听者
- 然后调用了监听器的starting()方法
- 就是通知所有的监听者
new DefaultApplicationArguments()
- 就是创建出这个对象并把我们启动时传的参数封装起来
prepareEnvironment()
- 这一步是准备环境
- 首先是创建环境类
- 然后是配置环境
- 调用监听器的environmentPrepared()方法
- 绑定环境到应用
- 环境这块没有深入研究就不说了
createApplicationContext()
- 这一步是创建容器,模板模式,根据不同的状态创建不同的容器
prepareContext
- 这一步是准备容器
- 设置环境context.setEnvironment()
- 调用ApplicationContextInitializer的initializer方法
- 调用监听器的contextPrepared()
- 初始化工厂
- 把参数类交给IOC管理
- 加载启动类到容器
- 调用监听器的ContextLoaded()方法
applyInitializers()
- 这一步调用所有实现了ApplicationContextInitializer的initialize()方法
refreshContext()
- 准备工作做完以后就要开始真正启动容器了
- 注册shutdownHook任务
- 调用refresh方法
- 我们是web类型所以调用的是ServletWebServerApplicationContext的方法
- 调用了父类的refresh方法
- 这一步看过Spring源码的应该知道这是Spring的启动过程,这里就不多说
- 重点看一下invokeBeanFactoryPostProcessors(beanFactory)和onRefresh()方法
- 首先看一下这个invokeBeanFactoryPostProcessors方法
- 参数为beanFactory为Spring的bean工厂,getBeanFactoryPostProcessors是获取上一步准备好的BeanFactoryPostProcessor,然后再看invokeBeanFactoryPostProcessors方法
invokeBeanFactoryPostProcessors(beanFactory,getBeanFActoryPostProcessors())
- 顾名思义这个ConfigClassPostProcessor就是来解析配置类的,所以我们进去看
- 执行了这个类的postProcessBeanDefinitionRegistry方法
- 再看这个类的最后一个方法,进去看
parse()
- 直接找到parser.parse方法,这里是Spring创建了一个ConfigurationClassParser来解析配置类
- 创建了一个配置类集合candidtes,我只有一个启动类所以只有一个,然后传入这个方法
- 进入这个parse方法
- 继续往下走
doProcessConfigurationClass()
- 找到下边这个doProcessConfigurationClass方法,这是真正解析的方法,解析完成后会放入configurationClasses这个Map中,接着进去看
- 首先判断自己是不是一个被@Component修饰的类
- 在判断自己是不是被@PropertySources修饰
- 然后再看有没有@ComponentScans这个注解,如果有会解析路径,然后获取所有在路径下需要被实例化的类
- 获取所有需要被实例化的类以后会对这些类进行parse,也就是会把解析启动类的流程对需要实例化的类再解析
- 解析完成后就会解析@Import注解,这步很关键,我们说完这个方法再说
- 然后解析@ImportResource注解
- 然后是@Bean注解
- 最后会判断这个被解析的类有没有父类,如果有则返回,刚才解析的do while的条件是返回的sourceClass如果不为空则继续循环,代表会把每个进入循环的类的父类进行解析
processImports()
- 我们去到@SpringBootApplication中,这是一个复合注解,我们主要看其中的@EnableAutoConfiguration这个注解
- 这里通过@Import导入了AutoConfigurationImportSelector这个类,这个类实现了DeferredImportSelector接口
- AutoConfigurationPackage则是导入了AutoConfigurationPackages.Registrar这个内部类,这个内部类实现了ImportBeanDefinitionRegistrar接口,
- 在前边第一次通过SpringFactoriesLoader.loadFactoryNames()这种SPI方式已经把key为EnableAutoConfiguration放入到了缓存中,我们只需要后边找到调用者就可以知道是如何实例化这些类的
- 如果不太明白@Import的使用可以看一下这篇文章
- @Import基本使用
- 然后回到刚才解析@Import的这个方法,这个方法的参数其中有一个是getImports(sourceClass),这一步是通过资源类通过@Import导入了哪些类,也就是刚才所导入的AutoConfigurationImportSelector和AutoConfigurationPackages.Registrar这两个类
- 这个方法主要是判断通过@Import导入的类有没有实现接口或者实现了什么接口
- 主要看AutoConfigurationImportSelector这个类,他是实现了DeferredImportSelector这个接口,所以会走this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector)这个方法
- 进来以后把这个类封装成了一个DeferredImportSelectorHolder对象,没有特殊情况的话deferredImportSelectors是不为null的,所以就把这个对象加入到了deferredImportSelectors这个集合中
- 然后我们退回到parse方法然后进入这个process()这个方法中
- 然后我们直接看解析的方法
- 这里是通过getImports()然后进行了遍历,我们看这个getImports方法
- 这个process方法的参数传入的就是我们的AutoConfigurationImportSelector,再跟进去看
- 这一步把我们传入的AutoConfigurationImportSelector对象转换成了原始类型,然后调用实现的getAutoConfigurationEntry方法,然后通过遍历返回对象的getAutoConfigurationEntry()方法来获取通过@Import导入的全类名并放入集合中
- 然后进来看getAutoConfigurationEntry()方法,这次是就是去缓存中通过EnableAutoConfiguration获取了需要实例化的集合并封装成一个AutoConfigurationEntry类
- 然后回到开始解析这边来,通过创建一个configClasses这个Set集合并把parser对象解析出来的全类名数组放进去,然后创建reader这个对象然后加载这些解析出来的全类目
- 然后会在Spring的finishBeanFactoryInitialization(beanFactory)讲需要创建的Bean对象进行创建,这一块下篇博客会专门说
- 然后我们回到这边Spring的确定方法继续往下看
- 这个类调用了父类的onRefresh方法后启动了Web容器
- 我们这里启动的是内置Tomcat
createWebServer()
- 在这一步获取web容器工厂然后根据状态创建实例
- 这也就是为什么SpringBoot能独立运行
- 首先通过getWebServerFactory()获取web容器工厂,实现类分别有Jetty,Tomcat等
- 这里我们启动的是Tomcat,所以直接看TomcatServletWebServerFactory的源码
- 实例化Tomcat然后配置Connector,这里是NIO方式运行,然后在配置其他的一些属性等,然后通过getTomcatWebServer(tomcat)将其封装为一个WebServer实现
afterRefresh()
- 这个方法在类中没有具体实现,应该是SpringBoot提供的一个扩展点
callRunners()
- 对实现了ApplicationRunner和CommandLineRunner的类进行方法调用
- 后边就是对监听器的一些方法调用等
总结
- SpringBoot的启动流程就是首先通过加载META-INF/spring.factories文件获取给定扩展出来的点进行加载,然后获取配置,准备启动容器,对Spring进行了封装,其实启动的还是Spring的容器然后将注解等方式注入的Bean通过处理器等方法加载到Spring中,然后通过内置启动Tomcat来进行独立运行并可以进行Web服务,最后在SpringBoot的扩展点等进行一下调用。
- 总的来说SpringBoot约定大于配置,独立运行,自动装配等功能让我们使用起来更加方便,让我们可以更好的专注业务而不是框架。
- 如果有不对的地方还望指出,以上只是个人理解