SpringBoot启动流程源码分析

前言【SpringBoot启动流程源码分析】SpringBoot项目的启动流程是很多面试官面试中高级Java程序员喜欢问的问题 。这个问题的答案涉及到了SpringBoot工程中的源码 , 也许我们之前看过别的大牛写过的有关SpringBoot项目启动流程的文章,但是自己没有去研究一遍总是会记忆不深刻 。有句话叫做“纸上来得终觉浅,绝知此事要躬行”,我觉得说得非常在理 。底层的东西 , 也只有自己深入研究过一遍甚至好几遍源码才能彻底搞懂并记忆牢固 。下面笔者来带领大家详细分析SpringBoot启动过程中到底做了哪些事情 , 把本文仔细看完了,面对面试官问的有关SpringBoot启动过程做了哪些工作的面试题就迎刃而解了!
启动类入口方法首先我们通过SpringApplication类的静态Run方法进入SpringBoot项目的启动入口
/*** @param primarySource springboot启动类* @param args 启动参数*/public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class[]{primarySource}, args);}复制
从上面的源码中我们可以看到SpringBoot启动类返回的应用上下文类是ConfigurableApplicationContext
然后我们进入另一个静态run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return (new SpringApplication(primarySources)).run(args);}复制
在上面这个静态run方法里面最终会通过SpringApplication类的构造函数实例化一个SpringApplication类实例对象,后面在调用SpringApplication实例对象的run方法
SpringApplication类实例化和初始化接下来我们看看SpringApplication类在实例化时做了什么事情
public SpringApplication(Class<?>... primarySources) {this((ResourceLoader)null, primarySources);}复制
可以看到在SpringApplication类上面这个构造方法里面又调用了另一个构造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {// 实例化sources属性this.sources = new LinkedHashSet();// 打印模式为控制台打印this.bannerMode = Mode.CONSOLE;// 设置记录启动日志信息标识为truethis.logStartupInfo = true;// 设置添加命令行属性标识为truethis.addCommandLineProperties = true;//设置addConversionService属性为truethis.addConversionService = true;// 设置headless属性为truethis.headless = true;// 设置注册应用关停钩子属性为truethis.registerShutdownHook = true;// 实例化additionalProfiles属性this.additionalProfiles = new HashSet();//默认非自定义环境this.isCustomEnvironment = false;// 上一步传过来的resourceLoader为nullthis.resourceLoader = resourceLoader;// 断言primarySources参数不能为空,也就是springboot应用类不能为空Assert.notNull(primarySources, "PrimarySources must not be null");// 将传递过来的springboot启动类参数转成List后加入LinkedHashSet集合后赋值给primarySources属性this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));// 根据类路径推断web应用类型this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));// 设置初始化器属性// 设置监听器属性this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));// 推断主启动类this.mainApplicationClass = this.deduceMainApplicationClass();}复制
从上面的源码中我们分析的结果来看,实例化SpringApplication类的过程做了以下几件事情:

  • 初始化SpringApplication启动类中的大部分属性变量
  • 推断web应用类型
  • 通过加载类路径目录META-INF下的spring.factories文件读取出初始化器和监听器集合并设置到SpringApplication实例对应的初始化器和监听器属性列表中
  • 推断主启动类并赋值给SpringApplication启动类的mainApplicationClass属性
推断Web应用类型进入WebApplicationType#deduceFromClasspath方法
private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};static WebApplicationType deduceFromClasspath() {if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {return REACTIVE;} else {String[] var0 = SERVLET_INDICATOR_CLASSES;int var1 = var0.length;for(int var2 = 0; var2 < var1; ++var2) {String className = var0[var2];if (!ClassUtils.isPresent(className, (ClassLoader)null)) {return NONE;}}return SERVLET;}}

推荐阅读