深入理解SpringBoot启动流程(包含自动装配和自定义starter是如何被实现)

源码版本

  • 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约定大于配置,独立运行,自动装配等功能让我们使用起来更加方便,让我们可以更好的专注业务而不是框架。
  • 如果有不对的地方还望指出,以上只是个人理解
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值