Spring Boot源码解析(一)SpringApplication初始化

介绍

本系列文章将从Spring Boot的main函数入口,一步一步带领大家阅读spring boot的源代码,并且会详细解释spring boot各个类和方法上注释的含义,帮助阅读英文有困难的同学更好的理解spring boot的原理,话不多说,直接开始。

程序入口

通常在一个spring boot的应用中,会看到下面一段代码作为应用的入口

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

那么这段代码究竟做了什么呢,让我们深入来分析它背后的原理。当我们点击run来查看源代码时,会看到下面这段代码,这段Java Doc说明这是一个助手方法,可以通过指定一个primarySource的source源来启动,这个primarySource其实就是我们的启动类Application

/**
 * Static helper that can be used to run a {@link SpringApplication} from the
 * specified source using default settings.
 * @param primarySource the primary source to load
 * @param args the application arguments (usually passed from a Java main method)
 * @return the running {@link ApplicationContext}
 */
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

接着,我们继续进入run方法,你会看到另外一个helper方法,这个helper方法首先初始化一个SpringApplication,然后再一次执行SpringApplication实例的run方法

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

初始化SpringApplication

我们将一行一行逐步讲解初始化SpringApplication的逻辑

@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));
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	this.bootstrapRegistryInitializers = new ArrayList<>(
			getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

初始化resourceLoader

resourceLoader 这里为空,当这个resourceLoader为空的时候,spring boot会使用缺省classLoader,这个resrouceLoader的作用后面会另外写一篇文章详细讲解,这里不影响我们对整体逻辑的理解,先跳过。

初始化webApplicationType

webApplicationType确定当前应用的类型,进入deduceFromClasspath()可以看到spring boot是如何确定类型的,从下面这段代码中可以看出当前应用属于哪一种类型取决于classpath中是否加载到了相应的类。

  • 如果classpath中有org.springframework.web.reactive.DispatcherHandler类,但是没有DispatcherServletServletContainer类,则类型为Reactive web application,会启动一个嵌入式的reactive web server。
  • 如果classpath中既没有javax.servlet.Servlet又没有 org.springframework.web.context.ConfigurableWebApplicationContext,那么该应用不是一个web application。
  • 如果不是以上两种,那么就判断为一个servlet的web application,会启动一个嵌入式的servlet web server。
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;
}

初始化BootstrapRegistryInitializer

我们再来看bootstrapRegistryInitializers,下面的代码简单来说就是通过反射加载在META-INF/spring.factories中所有的 bootstrapRegistryInitializer ,具体加载过程会在后续文章中详解

this.bootstrapRegistryInitializers = new ArrayList<>(
	getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

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

而BootstrapRegistryInitializer又是干什么的呢,我们为什么要加载BootstrapRegistryInitializer呢,我们从Java Doc里就可以明白这个接口是通过Initialize的方法来加载BootstrapRegistry的。

/**
 * Callback interface that can be used to initialize a {@link BootstrapRegistry} before it
 * is used.
 *
 * @author Phillip Webb
 * @since 2.4.5
 * @see SpringApplication#addBootstrapRegistryInitializer(BootstrapRegistryInitializer)
 * @see BootstrapRegistry
 */
@FunctionalInterface
public interface BootstrapRegistryInitializer {

	/**
	 * Initialize the given {@link BootstrapRegistry} with any required registrations.
	 * @param registry the registry to initialize
	 */
	void initialize(BootstrapRegistry registry);

}

可能又有同学要问,什么是 BootstrapRegistry,我们继续看Java Doc,继承这个注册接口的类会在ApplicationContext准备好之前可用,并且接口上有一个register的方法和一个addCloseListener

  • register: 以class为key来注册一些对象的实例来使用
  • addCloseListener: 在BootstrapContext关闭和ApplicationContext准备好之前调用该Listener

那么我们可以简单理解为这个BootstrapRegistry是为了让spring boot在 ApplicationContext准备好之前能有机会来注册一些类,并且通过触发ApplicationContext ready的事件来处理一些逻辑

/**
   
 * A simple object registry that is available during startup and {@link Environment}
 * post-processing up to the point that the {@link ApplicationContext} is prepared.
 * <p>
 * Can be used to register instances that may be expensive to create, or need to be shared
 * before the {@link ApplicationContext} is available.
 * <p>
 * The registry uses {@link Class} as a key, meaning that only a single instance of a
 * given type can be stored.
 * <p>
 * The {@link #addCloseListener(ApplicationListener)} method can be used to add a listener
 * that can perform actions when {@link BootstrapContext} has been closed and the
 * {@link ApplicationContext} is fully prepared. For example, an instance may choose to
 * register itself as a regular Spring bean so that it is available for the application to
 * use.
 *
 * @author Phillip Webb
 * @since 2.4.0
 * @see BootstrapContext
 * @see ConfigurableBootstrapContext
 */
public interface BootstrapRegistry {

	/**
	 * Register a specific type with the registry. If the specified type has already been
	 * registered and has not been obtained as a {@link Scope#SINGLETON singleton}, it
	 * will be replaced.
	 * @param <T> the instance type
	 * @param type the instance type
	 * @param instanceSupplier the instance supplier
	 */
	<T> void register(Class<T> type, InstanceSupplier<T> instanceSupplie

         /**
	 * Add an {@link ApplicationListener} that will be called with a
	 * {@link BootstrapContextClosedEvent} when the {@link BootstrapContext} is closed and
	 * the {@link ApplicationContext} has been prepared.
	 * @param listener the listener to add
	 */
	void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener);

	//....
}

初始化ApplicationContextInitializer

这里和初始化bootstrapRegistryInitializers的逻辑一致,通过getSpringFactoriesInstances最终获得META-INF/spring.factories中所有的ApplicationContextInitializer,而ApplicationContextInitializer是用来初始化ConfigurableApplicationContext,关于ConfigurableApplicationContext将在后续篇幅中介绍

初始化ApplicationListener

这里逻辑也和初始化bootstrapRegistryInitializers一致,通过getSpringFactoriesInstances最终获得META-INF/spring.factories中所有的ApplicationListener

deduceMainApplicationClass

这个方法比较巧妙,通过构造一个RuntimeException,然后得到StackTrace的方法,来获得main方法所在的类,这样做的原因是因为primarySource是作为数组形式传入方法的,如果有超过一个primarySource,那么就没法直接从primarySource来判断哪个source是main方法所在实例。

this.mainApplicationClass = deduceMainApplicationClass();
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;
}

结语

由于篇幅有限,我们将在后续文章中继续从下面的run方法开始讲解Spring Boot的后续加载过程。

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


版权声明:本文为xchann原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。