@SpringBootApplication

@SpringBootApplication 注解了 spring boot 应用的入口,我们从这个注解入手整理 spring boot 的启动过程。

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

@SpringBootApplication 本身是一个组合注解,其中包含的最重要的注解有 3 个:

  1. @SpringBootConfiguration 这个注解中包含了 @Configuration 注解,表示当前类用于 bean 的配置,将被自动的加入容器中。
  2. @ComponentScan 可以使用这个注解的属性让容器将其他的配置类加入。
  3. @EnableAutoConfiguration 前两个类在普通的 spring 应用中也会出现,不同的是这第三个注解,它将自动导入应用需要的类,在容器中装配。

@EnableAutoConfiguration 由引入了两个注解:

  1. @AutoConfigurationPackage 通过这个注解,引入用于实现自动注册的注册中心的类: AutoConfigurationPackages.Registrar.class
  2. 引入(这个注解并非直接在 @EnableAutoConfiguration 上,而是通过 @Import 引入) AutoConfigurationImportSelector.class 。这个注解通过实现 DeferredImportSelector 而实现了 ImportSelector 中的 selectImports 方法。这个方法的定义如下:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
			annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());

这个方法在容器初始化 bean 之前执行,用于从各个组件的 jar 包中返回需要加载的类。再由类加载器加载到 jvm 中。

下面是 getAutoConfigurationEntry 方法代码:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	configurations = removeDuplicates(configurations);
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);
	configurations = filter(configurations, autoConfigurationMetadata);
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);
}

其中 AutoConfigurationEntry 的定义如下:

protected static class AutoConfigurationEntry {
	private final List<String> configurations;
	private final Set<String> exclusions;
	private AutoConfigurationEntry() {
		this.configurations = Collections.emptyList();
		this.exclusions = Collections.emptySet();
	}
	/**
	 * Create an entry with the configurations that were contributed and their
	 * exclusions.
	 * @param configurations the configurations that should be imported
	 * @param exclusions the exclusions that were applied to the original list
	 */
	AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
		this.configurations = new ArrayList<>(configurations);
		this.exclusions = new HashSet<>(exclusions);
	}
	public List<String> getConfigurations() {
		return this.configurations;
	}
	public Set<String> getExclusions() {
		return this.exclusions;
	}
}

上面的方法主要是做了加载组件中的配置列表(也完成了多个组件的去重工作),同时加载组件中被排除的类。

在过程中使用了以下的一个方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
			getBeanClassLoader());
	Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
			+ "are using a custom packaging, make sure that file is correct.");
	return configurations;
}

再深入下去: SpringFactoriesLoader.loadFactoryNames

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

自动配置器会跟根据传入的 factoryType.getName() 到项目系统路径下所有的 spring.factories 文件中找到相应类型的 factory 的实现类,从而加载。

spring-boot-starter-xxx

selectImports 会将组件需要的类加载到 jvm 中,而 spring-boot-starter-xxx 则包含了这些组件。(如果你尝试去打开过 starter 依赖,一般来说,你会发现它实际上只有一个 .pom 文件,甚至没有一个 .class 和 .java)

SpringFactoriesLoader

@SpringBootApplication 的部分提供过这个类,这是一个工厂类的加载器,其中最重要的方法是 loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader)loadFactories(Class<T> factoryClass, ClassLoader classLoader) 。前者的参数为工厂方法的父类或接口,和一个类加载器,从这个类加载器的路径下(类加载器有相应的负责的类路径)下搜索给定工厂类的子类;后者调用前者,并根据前者的返回值初始化这些类。

配合 @EnableAutoConfiguration 使用的话,它更多是提供一种配置查找的功能支持,即根据 @EnableAutoConfiguration 的完整类名 org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为查找的 key,获取对应的一组 @Configuration 类,作为需要的组件的配置。

实例化 SpringApplication

启动 spring 应用的 run() 方法几经调用:

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

在真正运行 run() 方法时会先实例化 SpringApplication 对象。

/**
 * 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));
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

在这个构造中主要干了 4 件事:

  1. 判断当前程序的类型: WebApplicationType.deduceFromClasspath() 根据类路径中是否存在某个特征的类,有 NONE , SERVLET , REACTIVE 等类型。
  2. 获取工厂实例(即之前利用 SpringFactoriesLoader 加载的实例)。
  3. 实例化监听器(方式同上)。
  4. 配置应用主方法所在的类。

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

run 方法大致可分为:

  1. 收集各种条件和回调接口
    完成后发出 started() 通知
  2. 创建并配置 Environment
    完成后发出 environmentPrepared() 通知
  3. 创建并初始化 ApplicationContext 并设置 Environment 加载配置
    在各自完成后依次发出 contextPraparedcontextLoaded() 通知
  4. 刷新(refresh) ApplicationContext
    完成后执行 CommnadLineRunner 并通知 finished()