spring boot2.x —— SpringApplication

1、 直接开干

spring boot 项目主类启动入口

@SpringBootApplication //<1>
public class SrcSpringBootExampleTsshareApplication {

   public static void main(String[] args) {
      //<2>
      SpringApplication.run(SrcSpringBootExampleTsshareApplication.class, args);
   }

}

  • <1> @SpringBootApplication 为Spring Boot 项目启动类注解,标明是 Spring Boot 应用。通过它,可以开启自动配置的功能;
  • <2> SpringApplication#run(Class primarySource, String… args) 方法,启动Spring Boot 应用,Spring Boot 项目的核心处理都在这个方法里面处理,下面重点分析这个方法。

2、SpringApplication

//<1>

public static void main(String[] args) throws Exception {
   SpringApplication.run(new Class[0], args);
}
//<2>
public static ConfigurableApplicationContext run(Class primarySource, String... args) {
   return run(new Class[] { primarySource }, args);
}
//<3>
public static ConfigurableApplicationContext run(Class[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}
//<4>
public static int exit(ApplicationContext context, ExitCodeGenerator... exitCodeGenerators) {
   Assert.notNull(context, "Context must not be null");
   int exitCode = 0;
   try {
      try {
         ExitCodeGenerators generators = new ExitCodeGenerators();
         Collection beans = context.getBeansOfType(ExitCodeGenerator.class).values();
         generators.addAll(exitCodeGenerators);
         generators.addAll(beans);
         exitCode = generators.getExitCode();
         if (exitCode != 0) {
            context.publishEvent(new ExitCodeEvent(context, exitCode));
         }
      }
      finally {
         close(context);
      }
   }
   catch (Exception ex) {
      ex.printStackTrace();
      exitCode = (exitCode != 0) ? exitCode : 1;
   }
   return exitCode;
}
  • <1><2>最终调用第<3>个 ,提供Spring Boot 的运行入口
  • <4> 提供Spring Boot 关闭功能的一些退出动作

2.1 构造方法

public SpringApplication(Class... primarySources) {
   this(null, primarySources);
}

/**
 * Create a new {@link SpringApplication} instance. The application context will load
 * beans from the specified primary sources (see {@link SpringApplication class-level}
 * documentation for details. The instance can be customized before calling
 * {@link #run(String...)}.
 * @param resourceLoader the resource loader to use
 * @param primarySources the primary bean sources
 * @see #run(Class, String[])
 * @see #setSources(Set)
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
   //资源加载器
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   //通过 classpath ,判断 Web 应用类型。
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}
  • 根据构造方法来看该类的部分属性
  • resourceLoader :属性,资源加载器。有个大佬的文章可以参考看看【死磕 Spring】—— IoC 之 Spring 统一资源加载策略
  • primarySources :主要的 Java Config 类的数组,就是SrcSpringBootExampleTsshareApplication 类
  • webApplicationType :通过 classpath ,判断 Web 应用类型。REACTIVE(响应式web ,spring webflux),SERVLET(spring mvc),NONE(非web)
  • initializers:getSpringFactoriesInstances(ApplicationContextInitializer.class));调用SpringFactoriesLoader 加载在 META-INF/spring.factories 下ApplicationContextInitializer 初始化实例
  • spring boot2.x —— SpringApplication_第1张图片
  • listeners:setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    调用SpringFactoriesLoader 加载在 META-INF/spring.factories 下ApplicationListener 初始化实例
    spring boot2.x —— SpringApplication_第2张图片
  • mainApplicationClass:,调用 #deduceMainApplicationClass() 方法,获得是调用了哪个 #main(String[] args) 方法;这个 mainApplicationClass 属性,没有什么逻辑上的用途,主要就是用来打印下日志,说明是通过这个类启动 Spring 应用的。

    2.1.1 getSpringFactoriesInstances

    #getSpringFactoriesInstances(Class type) 方法,获得指定类类对应的对象们。代码如下:
private  Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
   // <1> 排序对象们加载指定类型对应的,在 `META-INF/spring.factories` 里的类名的数组
   Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   // <2>创建对象们
   List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   // <3>创建对象们
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}
  • <1> #SpringFactoriesLoader.loadFactoryNames(type, classLoader)
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
# 省略代码

public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
   MultiValueMap result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   try {
   // 通过类加载器获取资源  "META-INF/spring.factories" 下对应的map 资源
      Enumeration urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry entry : properties.entrySet()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
            for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               result.add(factoryTypeName, factoryImplementationName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}
  • <2> createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);根据第一步获取的结果进行创建对象实例
  • <3> 对实例进行排序,排序规则按照注解 @Order(num)

2.2 run方法

#run(String.. arg) 运行 Spring 应用。代码如下:
/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 * > 1. 配置属性
 * > 2。设置系统属性『java.awt.headless』
 * > 3. 获取监听器,发布应用开始启动事件
 * > 4. 初始化输入参数
 * > 5. 配置环境,
 * > 6. 输出 banner
 * > 7. 创建上下文
 * > 8. 实例化异常分析器
 * > 9. 预处理上下文
 * > 10. 刷新上下文
 * > 11. 再一次刷新上下文
 * > 12. 停止 StopWatch 统计时长
 * > 13. 打印 Spring Boot 启动的时长日志
 * > 14. 发布应用已经启动的事件
 * > 15. 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法
 * > 16. 通知 SpringApplicationRunListener 的数组,Spring 容器运行中
 */
public ConfigurableApplicationContext run(String... args) {
   //<1> 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection exceptionReporters = new ArrayList<>();
   /**<2> 设置系统属性『java.awt.headless』,为true则启用headless模式支持**/
   configureHeadlessProperty();
   /***<3>
    * 通过SpringFactoriesLoader 加载 *META-INF/spring.factories
    * 找到声明的所有SpringApplicationRunListener的实现类并将其实例化,
    * /之后逐个调用其started()方法,广播SpringBoot要开始执行了
    */
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      //<4>初始化参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //<5>创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
      //并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      //<6>打印banner
      Banner printedBanner = printBanner(environment);
      //<7>创建应用上下文 spring 容器
      context = createApplicationContext();
      //<8> 通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,获取并实例化异常分析器
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      //<9> 为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
      //并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
      //之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,
      //这里就包括通过**@EnableAutoConfiguration**导入的各种自动配置类。
      //主要是调用所有初始化类的 initialize 方法
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      //<10> 刷新上下文 启动spring 容器
      refreshContext(context);
      //<11> 再一次刷新上下文( 执行 Spring 容器的初始化的后置逻辑。默认实现为空)   ,其实是空方法,可能是为了后续扩展。 。
      afterRefresh(context, applicationArguments);
      //<12>停止 StopWatch 统计时长
      stopWatch.stop();
      //<13> 打印 Spring Boot 启动的时长日志
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      //<14> 发布应用已经启动的事件  通知 SpringApplicationRunListener 的数组,Spring 容器启动完成
      listeners.started(context);
      //<15> 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
      //我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      //<16>通知 SpringApplicationRunListener 的数组,Spring 容器运行中
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。

  • <2> 设置系统属性『java.awt.headless』,为true则启用headless模式支持
  • <3> 获取监听器,发布应用开始启动事件,依然根据 2.1.1 中方式对SpringFactoriesLoader 加载 *META-INF/spring.factories
    找到声明的所有SpringApplicationRunListener的实现类并将其实例化,之后逐个调用其started()方法,广播SpringBoot要开始执行了
  • <4> 初始化参数 ApplicationArguments 对象
  • <5> 加载属性配置,创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile) spring boot2.x —— SpringApplication_第3张图片
    spring boot2.x —— SpringApplication_第4张图片
    并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕
    spring boot2.x —— SpringApplication_第5张图片
  • <6> #printBanner(environment);打印banner;
    spring boot2.x —— SpringApplication_第6张图片
  • <7> #createApplicationContext(); 创建应用上下文,也就是创建 Spring 容器;
    spring boot2.x —— SpringApplication_第7张图片
    • 根据初始化SpringApplication是类路径中初始化的webApplicationType来选择对应的容器,这里我们是SERVLET,他会通过Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);防区是初始化一个需要的SERVLET容器
  • <8> #getSpringFactoriesInstances 方式获取并创建SpringBootExceptionReporter 异常报告器实例集合,记录启动过程中的异常信息。以下是exceptionReporters 的实体
    spring boot2.x —— SpringApplication_第8张图片
    spring boot2.x —— SpringApplication_第9张图片
    包括18个FailureAnalyzer 错误分析器
  • <9> #prepareContext(context, environment, listeners, applicationArguments, printedBanner); 预处理上下文,主要是调用所有初始化类的 #initialize(…) 方法 ,因为方法内容较多,详细见 【2.2.1 】,下面
  • <10> #refreshContext(context); 刷新上下文 启动spring 容器 详细见 【2.2.2】,下面
  • <11> #afterRefresh(context, applicationArguments); 再一次刷新应用上下文,表示上下文被刷新后调用,这是一个空方法,主要是为做一些上下刷新后的拓展备用;
    spring boot2.x —— SpringApplication_第10张图片
  • <12> 停止 StopWatch 统计时长;
  • <13> 打印 Spring Boot 启动的时长日志;
    在这里插入图片描述
  • <14> #listeners.started(context); 发布应用已经启动的事件 通知 SpringApplicationRunListener 的数组,Spring 容器启动完成
  • <15> #callRunners(context, applicationArguments); 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法;这一步我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。详细见【2.2.3】;
  • <16> listeners.running(context); 循环调用SpringApplicationRunListener通知 SpringApplicationRunListener 的数组,Spring 容器运行中,整体的run 方法结束,其他是些run 过程中的异常处理代码,如果发生异常,则调用 #handleRunFailure(…) 方法,交给 SpringBootExceptionReporter 进行处理,并抛出 IllegalStateException 异常。

以下是针对上面run 方法中一些复杂方法的单独拆分分析

2.2.1 #prepareContext(context, environment, listeners, applicationArguments, printedBanner); 预处理上下文

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   //<1> 设置context的environment 属性
   context.setEnvironment(environment);
   //<2> 设置context 一些属性
   postProcessApplicationContext(context);
   //<3> 初始化 ApplicationContextInitializer
   applyInitializers(context);
   //<4> 告诉SpringApplicationRunListener 监听器上下文初始化完毕
   listeners.contextPrepared(context);
   // <5> 打印日志
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
   // <6> 设置 beanFactory 的属性
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   // Load the sources
   // <7> 加载 BeanDefinition 们
   Set sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[0]));
   // <8> 通知 SpringApplicationRunListener 的数组,Spring 容器加载完成。
   listeners.contextLoaded(context);
}
 
  
  • 这个方法代码相对比较多,主要是给上下文 context 做些初始化的赋值操作,为后面启动做准备
  • <1> 设置 context 的 environment 属性
  • <2> 调用 #postProcessApplicationContext(ConfigurableApplicationContext context) 方法,设置 context 的一些属性
  • <3> 调用 #applyInitializers(ConfigurableApplicationContext context) 方法,初始化 ApplicationContextInitializer ,遍历 ApplicationContextInitializer 数组,逐个调用 ApplicationContextInitializer#initialize(context) 方法,进行初始化。
  • <4> #listeners.contextPrepared(context); 告诉SpringApplicationRunListener 监听器上下文初始化完毕
  • <5> 打印日志
    在这里插入图片描述
  • <6> 设置 beanFactory 的属性, 包括 应用参数 applicationArguments,打印的banner printedBanner,还有
if (this.lazyInitialization) {
   context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}

根据懒加载的属性来设置懒加载的beanFactoryPostProcessor配置

  • <7> #load(ApplicationContext context, Object[] sources) 方法,加载 BeanDefinition 们
protected void load(ApplicationContext context, Object[] sources) {
   if (logger.isDebugEnabled()) {
      logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
   }
   // <1> 创建 BeanDefinitionLoader 对象
   BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
   // <2> 设置 loader 的属性
   if (this.beanNameGenerator != null) {
      loader.setBeanNameGenerator(this.beanNameGenerator);
   }
   if (this.resourceLoader != null) {
      loader.setResourceLoader(this.resourceLoader);
   }
   if (this.environment != null) {
      loader.setEnvironment(this.environment);
   }
   // <3> 执行 BeanDefinition 加载
   loader.load();
}

    • <1> 处,调用 #getBeanDefinitionRegistry(ApplicationContext context) 方法,创建 BeanDefinitionRegistry 对象
      spring boot2.x —— SpringApplication_第11张图片
      关于 BeanDefinitionRegistry 类,暂时不需要深入了解。感兴趣的胖友,可以看看 《【死磕 Spring】—— IoC 之 BeanDefinition 注册表:BeanDefinitionRegistry》 文章。

      • <2> 设置 loader 的属性
      • <3> 执行 BeanDefinition 加载 ,具体文章见 芋艿大佬的文章《【死磕 Spring】—— IoC 之加载 BeanDefinition》

*<8> #contextLoaded(ConfigurableApplicationContext context) 方法,通知 SpringApplicationRunListener 的数组,Spring 容器加载完成

2.2.2 refreshContext(context); 刷新上下文,启动spring 容器

private void refreshContext(ConfigurableApplicationContext context) {
   //<1> 开启(刷新)Spring 容器
   refresh(context);
   //<2> 注册ShutdownHook 钩子
   if (this.registerShutdownHook) {
      try {
         context.registerShutdownHook();
      }
      catch (AccessControlException ex) {
         // Not allowed in some environments.
      }
   }
}
  • <1> 调用 AbstractApplicationContext# refresh(context);
    spring boot2.x —— SpringApplication_第12张图片
    这个方法为启动容器最为核心的方法,处理的内容比较多,这里也会触发可以触发 Spring Boot 的自动配置的功能,包括tomcat的实例的创建和启动等 下面分析回着重细致讲解。

  • <2> ConfigurableApplicationContext#registerShutdownHook() 方法,注册 ShutdownHook 钩子。这个钩子,主要用于 Spring 应用的关闭时,销毁相应的 Bean 们

2.2.3 callRunners(context, applicationArguments);

private void callRunners(ApplicationContext context, ApplicationArguments args) {
   // <1> 获得所有 Runner 们 ApplicationRunner & CommandLineRunner
   List runners = new ArrayList<>();
   runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
   // <2> 排序 runners
   AnnotationAwareOrderComparator.sort(runners);
   // <3> 遍历 Runner 数组,调用执行逻辑
   for (Object runner : new LinkedHashSet<>(runners)) {
      if (runner instanceof ApplicationRunner) {
         callRunner((ApplicationRunner) runner, args);
      }
      if (runner instanceof CommandLineRunner) {
         callRunner((CommandLineRunner) runner, args);
      }
   }
}

 
  
  • <1> 获得所有 Runner 们 ApplicationRunner & CommandLineRunner;
  • <2> 排序 runners;
  • <3> 遍历 Runner 数组,调用执行逻辑
    spring boot2.x —— SpringApplication_第13张图片
    spring boot2.x —— SpringApplication_第14张图片
    关于 Runner 功能的使用,可以找点文档自己看看。

参考文档:http://svip.iocoder.cn/Spring-Boot/SpringApplication ,芋艿源码–只是星球进行梳理。大家一起学习和进步。

你可能感兴趣的:(springboot)