Springboot 启动流程源码主流程分析

Springboot 启动流程源码主流程分析

一 、 前半部分new SpringApplication(primarySources)

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    
    
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}
  1. WebApplicationType.deduceFromClasspath(); 是根据所有加载的类中,是否存在下面的几个类,判断是什么类型的应用,设置到SpringApplication类的webApplicationType属性上,后面创建容器的时候(二中的6createApplicationContext() ),根据它来判断该创建创建那种类型的容器。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
private static final String[] SERVLET_INDICATOR_CLASSES = {
    
     "javax.servlet.Servlet",
      "org.springframework.web.context.ConfigurableWebApplicationContext" };

private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
      + "web.servlet.DispatcherServlet";

private static final String WEBFLUX_INDICATOR_CLASS = "org."
      + "springframework.web.reactive.DispatcherHandler";

private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

static WebApplicationType deduceFromClasspath() {
    
    
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    
    
      return WebApplicationType.REACTIVE;
   }
   for (String className : SERVLET_INDICATOR_CLASSES) {
    
    
      if (!ClassUtils.isPresent(className, null)) {
    
    
         return WebApplicationType.NONE;
      }
   }
   return WebApplicationType.SERVLET;
}
  1. 设置初始化的类,从META-INF/spring.factories中获取ApplicationContextInitializer指定的所有类,设置到SpringApplication的initializers属性上。
setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));

这个里面getSpringFactoriesInstances 会获取META-INF/spring.factories 文件中指定的类,是获取所有当前应用中,jar包目录中有META-INF/spring.factories文件,而不是某一个jar包下的。下图是我调试时候的截图。后续还会调用很多次这种getSpringFactoriesInstances方法,都从MultiValueMap类型的缓存中取,只在第一次的时候从文件中读取,之后就缓存了。
在这里插入图片描述
3. 和2类似, 这次取得是ApplicationListener指定的类。

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  1. 根拒线程堆栈,找到main方法所在的类,主要用来打日志。
this.mainApplicationClass = deduceMainApplicationClass();

二 、接下来看看后半部分的run方法。

public ConfigurableApplicationContext run(String... args) {
    
    
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
  this.configureHeadlessProperty();
  SpringApplicationRunListeners listeners = this.getRunListeners(args);
  listeners.starting();

  Collection exceptionReporters;
  try {
    
    
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
    this.configureIgnoreBeanInfo(environment);
    Banner printedBanner = this.printBanner(environment);
    context = this.createApplicationContext();
    exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{
    
    ConfigurableApplicationContext.class}, context);
    this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    this.refreshContext(context);
    this.afterRefresh(context, applicationArguments);
    stopWatch.stop();
    if (this.logStartupInfo) {
    
    
      (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
    }

    listeners.started(context);
    this.callRunners(context, applicationArguments);
  } catch (Throwable var10) {
    
    
    this.handleRunFailure(context, var10, exceptionReporters, listeners);
    throw new IllegalStateException(var10);
  }

  try {
    
    
    listeners.running(context);
    return context;
  } catch (Throwable var9) {
    
    
    this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
    throw new IllegalStateException(var9);
  }
}
  1. 计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
stopWatch.stop();

stopWatch是一个计时器,可以统计start到stop之间所用的时间。

  1. 设置系统属性java.awt.headless
this.configureHeadlessProperty();

这一句是表示运行在服务器端,在没有显示器和鼠标键盘的模式下工作,模拟输入输出设备功能

  1. 加载容器启动监听器
SpringApplicationRunListeners listeners=this.getRunListeners(args);
listeners.starting();

这两句的作用是:

  • 从META-INF/spring.factories 配置中获取org.springframework.boot.SpringApplicationRunListener指定的类,只有一个org.springframework.boot.context.event.EventPublishingRunListener。加载并实例化
  • 加载并实例化以后,调用他们的starting()方法。这个方法里面发布了一个ApplicationStartingEvent()事件。这个事件有四个支持的监听者,如下图
    在这里插入图片描述
    ​ 依次调用每个监听者的onApplicationEvent()方法,例如LoggingApplicationListener的onApplicationEvent() 方法就是判断一下是那种日志系统,log4j、logback等等,然后执行一些日志系统初始化的工作。
  1. 获取配置
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
                                                         applicationArguments);
configureIgnoreBeanInfo(environment);

这几句的作用是获取系统的环境变量、系统属性、还有我们自己的application.properties文件中的配置,下图是调试时候的截图,可以看到application.properties中的配置就是在prepareEnvironment(listeners, applicationArguments);这一步读取进来的。
在这里插入图片描述
同时发布ApplicationEnvironmentPreparedEvent事件,调用每个监听者的监听方法。
在这里插入图片描述

  1. 这个打印logo
Banner printedBanner = printBanner(environment);
  1. 获取ConfigurableApplicationContext,就是根据1中设置的容器类型,创建容器类。
context = createApplicationContext();
  1. 加载org.springframework.boot.diagnostics.FailureAnalyzers 指定的类。这些类主要是在项目启动失败之后,处理异常,打印日志等,他是在启动异常的catch块中调用的:
exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] {
    
     ConfigurableApplicationContext.class }, context);
catch (Throwable ex) {
    
    
   handleRunFailure(context, ex, exceptionReporters, listeners);
   throw new IllegalStateException(ex);
}
  1. 准备容器。 这一步主要是在容器刷新之前的准备动作:把之前解析好的环境environment、容器监听器listeners、启动时的参数applicationArguments等属性设置到容器对象。其中包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。 容器能加载到启动类,就能解析到启动类上的注解,就能逐个的加载要扫描的类,包括各种配置类及他们的初始化工作。
prepareContext(context, environment, listeners, applicationArguments,
      printedBanner);
private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
    
    
		//设置容器环境,包括各种变量
		context.setEnvironment(environment);
		//执行容器后置处理
		postProcessApplicationContext(context);
		//执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
		applyInitializers(context);
		//发送容器已经准备好的事件,通知各监听器
		listeners.contextPrepared(context);
		//打印log
		if (this.logStartupInfo) {
    
    
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		//注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		//设置banner
		if (printedBanner != null) {
    
    
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}
		// Load the sources
		//获取我们的启动类指定的参数,可以是多个
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		//加载我们的启动类,将启动类注入容器
		load(context, sources.toArray(new Object[0]));
		//发布容器已加载事件。
		listeners.contextLoaded(context);
	}
  1. 刷新容器。 spring的 ioc 和 aop的功能就在这里实现。比较复杂。后面再来。
refreshContext(context);
  1. 刷新容器后的扩展接口, 设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
afterRefresh(context, applicationArguments);
  1. 发布容器启动结束事件,有两个监听者,但是什么事情都没有做。
listeners.started(context);
  1. 执行所有实现了ApplicationRunner、CommandLineRunner接口的类中的run方法,经常用来自定义在容器启动启动完成后立即要做的事情。
callRunners(context, applicationArguments);
  1. 继续发布org.springframework.boot.context.event.ApplicationReadyEvent事件,调用该事件的监听器的监听方法。
try {
    
    
    listeners.running(context);
}

有两个作者我觉得写得比较好的,我也参考了许多:
https://blog.csdn.net/qq_26000415/column/info/18708 写得很详细
https://blog.csdn.net/woshilijiuyi/article/details/82219585 写得很精髓

猜你喜欢

转载自blog.csdn.net/u013041642/article/details/96036872