Spring启动流程

200 阅读3分钟

本文正在参加「Java主题月 - Java开发实战」,详情查看:juejin.cn/post/696719…

这是我参与更文挑战的第7天,活动详情查看: 更文挑战

开始

在Spring boot中,我们总是以一行代码启动我们的应用:

SpringApplication.run(???.class, args);

事实上我们调用的方法是:

/**
在SpringApplication中
*/
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

//--------调用

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

//-----调用

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

这其中就包括了:

  • 初始化SpringApplication
  • SpringApplication.run

在阅读源码之前,作为一个对于Spring了解仅限于使用的人而言,我觉得对于Spring,我希望它在启动的时候,做以下工作:

  • 注入配置项
  • 将我需要的对象,生成并放入对应的引用中
  • 连接数据库、redis以及其他配置文件中的配置
  • ......

那么我们带着我们的目的,来查看如何做这些工作的。

  • 框架的提出是为了解决一些繁杂重复性的工作,因此最底层的实现未必是复杂的。

初始化

接下来我们来解析SpringApplication的构造函数。

调用的构造方法的最底层代码如下:

/**
在SpringApplication中
*/

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();
	}

由于我们带进来的参数是 null,因此此处的resourceLoader就是null。

在这一步我们初始化了:

  • primarySources
  • webApplicationType
  • mainApplicationClass

并唤醒了注册的初始化器和监听器。接下来我们对涉及的方法进行分析。

判断application类型

我们来看看:

WebApplicationType.deduceFromClasspath()

这部分代码都做了什么工作。

该部分源码如下:

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;
	}

这一部分会判断我们启动的类型,此处我们启动的类型就是SERVLET了。

  • 通过Spring封装的classUtils,使用反射来看看启动的类是否是某些类。

设置初始化器

这部分从

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

进入。我们需要解析:

  • setInitializer
  • getSpringFactoriesInstances

getSpringFactoriesInstances

这部分代码如下:


private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
   return getSpringFactoriesInstances(type, new Class<?>[] {});
}
//

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

流程如下:

  • 获取ClassLoader

    • 这一步从当前线程获取classLoader。
  • 通过获取到的classLoader,将工厂名字取出。

    • 这一步对应方法:

      SpringFactoriesLoader.loadFactoryNames(type, classLoader)

  • 根据上一步取得的工厂名字,将工厂初始化。

  • 根据AnnotationAwareOrderComparator,对获得的工厂实例排序

  • 返回工厂实例。

setInitializer

实际上就是简单赋值而已。

ApplicationContextInitializer

//todo

设置监听器

这部分和上面类似,对应代码:

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

setListeners也就是简单的set方法。

获取主application类

对应代码为:

	private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

获取当前runtime的运行栈并取得当前执行main方法的类。

run!

构造函数解决完了,实际上主要就是对一些配置和监听器,初始化构造器进行了赋值的操作。

接下来解析run方法。

这部分代码如下:

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}
  1. 一开始先开了一个stopWatch,这步主要是为了记录我们启动项目总共花了多少时间。

  2. 配置了一个无头的Property。(这部分暂时条过,不太清楚是干什么用的(//todo))

  3. 接下来获取执行的listeners。

    • 这一步逻辑跟上面的setListener类似。
  4. 唤醒上一步设置的listeners(//todo )。

    对应代码为:listeners.starting();

    • 这里使用了观察设计模式:

      	void starting() {
      		for (SpringApplicationRunListener listener : this.listeners) {
      			listener.starting();
      		}
      	}
      

      实际上在这里我们只注册了一个listener:EventPublishingRunListener

      于是我们的调用到了这里:

      //in EventPublishingRunListener
      
      	public void starting() {
      		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
      	}
      
      	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
      		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
      		Executor executor = getTaskExecutor();
      		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
      			if (executor != null) {
      				executor.execute(() -> invokeListener(listener, event));
      			}
      			else {
      				invokeListener(listener, event);
      			}
      		}
      	}
      

      在这里会解析我们输入事件的属性,并分发给对应的监听者。

  5. 配置application启动参数(我们输入的是null)。

  6. 配置环境。

    这里根据我们初始化中获得的webApplicationType,进行environment的初始化。

    同时:

    • 将环境准备的工作,通知到所有注册的listener上并使其运行。
    • 将环境绑定到Spring上。
  7. 配置忽略的bean。

  8. 打印banner。

  9. 通过webApplicationType,创建applicationContext。

  10. 准备上下文。(//todo,类似6)

  11. 刷新上下文

  12. 启动listeners。

  13. 启动runners。