SpringBoot源码分析(一):run之前的准备工作

140 阅读3分钟

为了更好的学习和巩固SpringBoot的知识体系,我们需要通过研究源码去了解清楚SpringBoot究竟为我们做了什么?怎么做的?有哪些地方是我们可以定制化的?接下来我们就通过源码的分析来解开这些问题。

SpringBoot的启动

我们在写SpringBoot应用程序的时候都会定义一个SpringBoot的启动类,这个类的命名没有要求,但是需要在类上加上@SpringBootApplication注解来表示这个类时SpringBoot的启动类,并且此类的main方法中,需要调用:SpringApplication.run(xxx.class,args);来将SpringBoot应用跑起来,那么具体其中实现了什么事情呢?我们深入源码中探究一下。

run方法

    /**
    * 静态助手,可用于使用默认设置从指定源运行SpringApplication 。
    */
public static ConfigurableApplicationContext run(Class<?> primarySource,String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

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

我们进入SpringApplication的run方法之后发现注释上主要是表达run方法是用来使用默认配置,并从我们指定的源来运行SpringBoot应用的。 可以看到run方法中首先会通过我们传入的启动类创建一个SpringApplication,再由这个SpringApplication实例调用实际处理的run方法去实现启动过程。

实际执行业务逻辑的run方法

/**
* 运行Spring应用程序,创建并刷新一个新的ApplicationContext 
*/
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;
}

现在分析的核心先不放在run方法上,而是转向SpringApplication的实例化中,去看看在真正run之前,SpringBoot做了什么样的准备工作。

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

我们通过通读构造器中每个方法的名字大概推测一下实例化都做了哪些事:

  1. 设置资源加载器,默认是null;
  2. 断言源不能为空;
  3. 将源设置到成员变量中;
  4. 推测Web应用类型\color{red}{推测Web应用类型}
  5. 设置初始化器\color{red}{设置初始化器}
  6. 设置监听器\color{red}{设置监听器}
  7. 推测主类\color{red}{推测主类}。 其中重点放在标红的四个事件上,这四件事就是实例化中最主要的作用。

推测Web应用类型

我们进入WebApplicationType.deduceFromClasspath()看一下是如何推测Web应用类型的。

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

private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

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

可以看到deduceFromClasspath()方法是通过判断程序中是否有对应的类来判断web应用的类型的。

  • 如果存在DispatcherHandler且没有DispatcherServletServletContainer就视为REACTIVE类型
  • 如果不存在ServletConfigurableWebApplicationContext则为NONE类型
  • 其他情况就为SERVLET类型 正常应用的情况下一般都是SERVLET类型。

设置初始化器

setInitializers()方法设置初始化器,我们看到其中传入的参数是getSpringFactoriesInstances(ApplicationContextInitializer.class),这个方法是Spring非常常见的一个方法,我们也需要重点了解。

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

整个方法的流程大概是:

  1. 获取资源加载器,如果没有的话就获取默认的资源加载器;
  2. 通过传入的ApplicationContextInitializer.class和资源加载器,获取Factory的名字;
  3. 通过名字创建SpringFactories实例;
  4. 将实例进行排序(通过@Order注解进行排序)。 流程大致是这样的,但是我们还有疑问,它是从哪里获取到Factory的名字的呢?我们进入SpringFactoriesLoader中就能看到它是从META-INF/spring.factories文件去读取与ApplicationContextInitializer匹配的所有Factory名字的,SpringBoot中一共有两个:
  • 一个是spring-boot包下的,
  • 一个是spring-boot-autoconfiguration包下的。 通过查看spring.factories文件我们发现了一共有6个初始化器:
  • ConfigurationWarningsApplicationContextInitializer
  • ContextIdApplicationContextInitializer
  • DelegatingApplicationContextInitializer
  • ServerPortInfoApplicationContextInitializer
  • SharedMetadataReaderFactoryContextInitializer
  • ConditionEvaluationReportLoggingListener 到这里就知道了这个getSpringFactoriesInstances(ApplicationContextInitializer.class)是向setInitializers传入了6个初始化器,接下来就将初始化器设置到成员变量initializers中供后续方法使用。
public void setInitializers(
			Collection<? extends ApplicationContextInitializer<?>> initializers) {
	this.initializers = new ArrayList<>();
	this.initializers.addAll(initializers);
}

设置监听器

设置监听器的过程与设置初始化器类似,就不重复分析了,这里会加载10个监听器到 listeners成员变量中:

  • ClearCachesApplicationListener
  • ParentContextCloserApplicationListener
  • FileEncodingApplicationListener
  • AnsiOutputApplicationListener
  • ConfigFileApplicationListener
  • DelegatingApplicationListener
  • ClasspathLoggingApplicationListener
  • LoggingApplicationListener
  • LiquibaseServiceLocatorApplicationListener
  • BackgroundPreinitializer 这些监听器会在后续的流程中被使用。

推测主类

最后要推测主类,并设置到mainApplicationClass成员变量中。

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

具体是通过栈跟踪找到方法名为main的方法,它对应的类就是主类。

总结

我们通过源码分析就可以了解到在run方法执行前,会先经过SpringApplication的实例化,实例化过程主要进行了成员变量的设置:

  • 资源加载器:resourceLoader,默认是null;
  • 主源:primarySource(就是应用了@SpringBootApplication注解的类);
  • web应用类型:WebApplicationType,默认是SERVLET;
  • 初始化器:initializers,6个;
  • 监听器:listeners,10个;
  • 主应用程序类:mainApplicationClass,含有main方法的类。