团队新同学反馈还没有梳理清楚 SpringFramework 中一些八股文的东西,例如,Spring Boot 的启动流程,Spring Bean 的生命周期管理等。所以打算做几期 Spring Boot 相关分享,做这类分享当然不是鼓励大家去背八股文,主要目的还是从 SpringFramework 中的设计思路入手,思考这些设计模式都有什么优势,如何做的抽象,是否应该应用在自己的业务中。在熟练掌握了这些基础的技术点后,就能更正确的姿势使用 SpringFramwork 提供的扩展点做业务定制,使用更优雅的方式编写代码。

使用 Spring Cloud 做微服务开发的同学都知道应用启动时会优先从 bootstrap 配置文件中加载配置,那这篇文章我们从 Spring Boot 启动流程入手解析 Spring Cloud 的 Bootstrap Environment 创建过程。

1. 版本信息

versions:

spring-boot version: 2.3.4.RELEASE

spring-cloud version: 2.2.6.RELEASE

首先看下这两个 spring.factories ,在下面的启动主函数会对其进行说明,我先在这里插个眼。

~{mvn_home}\org\springframework\boot\spring-boot\2.3.4.RELEASE\spring-boot-2.3.4.RELEASE.jar!\META-INF\spring.factories

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

~{mvn_home}\org\springframework\cloud\spring-cloud-context\2.2.6.RELEASE\spring-cloud-context-2.2.6.RELEASE.jar!\META-INF\spring.factories

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\

2. 启动主函数

Spring Boot 的启动主函数大家应该是再熟悉不过了吧,这里就贴一下代码,具体流程先不赘述了。

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

先来看下 SpringApplicationRunListeners 类中的容器启动过程监听器 —— SpringApplicationRunListener ,该接口是 Spring 容器在整个启动过程的事件监听器,贯穿在启动中的各个阶段并触发相应的事件。getRunListeners(args) 方法通过扫描 spring.factories 文件并对所有类型为 SpringApplicationRunListener 的类进行实例化,其中就包含上面提到的 EventPublishingRunListener,顾名思义其会在启动过程中通过其成员变量 ApplicationEventMulticaster 广播器将对应的事件广播出去,类型就是大家很熟悉的 SpringApplicationEvent ,大家在业务开发中应该会经常使用这个事件做一些需要解耦的逻辑, ApplicationListener 会对相对应的事件进行处理,从而达到逻辑解耦。

介绍完 EventPublishingRunListener 容器启动事件监听器, 我们回到主函数 prepareEnvironment(listeners, applicationArguments) 方法中对照源码分析下 Environment 的创建过程。

    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(environment);

getOrCreateEnvironment()方法是用来创建当前 ApplicationContext 下的 Environment 实例。

if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}

由于当前还没有创建 Environment 对象,所以根据成员变量 webApplicationType 创建 Environment 实例。

成员变量webApplicationType是通过classpath中推断而来。

在构建 Environment 实例后,SpringApplicationRunListeners 就触发了 environmentPrepared() 方法,随后 EventPublishingRunListener 将 ApplicationEnvironmentPreparedEvent 事件广播出去。

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

接下来我们看看 Spring 是如何先从 bootstrap 配置文件中加载配置的。在 enironment 准备好后,EventPublishingRunListener 中发布了 ApplicationEnvironmentPreparedEvent 事件,到这里你们应该也猜到了,监听该事件其中之一正是上面 spring.factories 中的 BootstrapApplicationListener, 忘了的同学可以回到上面插眼那里再看下。

到目前为止 Spring Boot 启动主流程到 prepareEnvironment(),我们先将其先挂起,下面我们对照源码分析一下 BootstrapApplicationListener 中的事件监听方法如何先从 bootstrap 配置文件中读取配置并创建 Bootstrap 应用容器。

3. Bootstrap environment和容器创建流程

前面提到的 BootstrapApplicationListener 这个事件监听器就是用来创建 Bootstrap Environment 和 ApplicationContext。对照源码看下, BootstrapApplicationListener 是如何从 bootstrap.yml 配置文件读取配置并创建容器的。

private ConfigurableApplicationContext bootstrapServiceContext(
			ConfigurableEnvironment environment, final SpringApplication application,
			String configName) {
		StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
		MutablePropertySources bootstrapProperties = bootstrapEnvironment
				.getPropertySources();				
		...	
		SpringApplicationBuilder builder = new SpringApplicationBuilder()
				.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
				.environment(bootstrapEnvironment)
				.registerShutdownHook(false).logStartupInfo(false)
				.web(WebApplicationType.NONE);
        ...
		final SpringApplication builderApplication = builder.application();
		...
		builder.sources(BootstrapImportSelectorConfiguration.class);
		final Configurable ApplicationContext context = builder.run();
		context.setId("bootstrap");
		addAncestorInitializer(application, context);
		bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
		mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
		return context;

首先 bootstrapServiceContext() 方法创建了 StandardEnvironment 实例作为 Bootstrap Environment,并通过 Builder 模式创建了 Bootstrap 应用容器。值得注意的是在 Builder 构建过程中使用了 BootstrapImportSelectorConfiguration.class 作为应用容器的 source, 从命名的方式我们应该就猜到了该类用于加载 Bootstrap 相关配置类,不得不说 Spring 中的命名很值得大家学习。


@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {

}

SpringApplication 通过 source 来加载相关配置。最终使用builder.run()创建应用容器。BootstrapImportSelector.class 该标记接口是用来从 spring.factories 文件中加载类型为 BootstrapConfiguration.class 的类, 下面到代码中看下都加载了哪些类。

SpringApplicationBuilder是用来创建应用容器的创建者。

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.cloud.util.random.CachedRandomPropertySourceAutoConfiguration

其中 PropertySourceBootstrapConfiguration 正是我们要找的 bootstrap 配置加载类。其是 ApplicationContextInitializer 的实现类,里面的 initialize() 方法就是用于加载工程中的 bootstrap 配置文件,大家可以到对应方法中看下代码,这里就不赘述了。

4. 总结

作为新人分享的第一篇文章,从 Spring Boot 启动入手,对 Spring Cloud 中 Bootstrap Environment和应用容器的创建进行说明。整个加载的流程读起来非常简洁,Spring Boot 的这种将主流程和业务逻辑解耦的设计以及在 Listener 中扩展我们自己的业务逻辑等等都很值得我们进行思考。之后我会对 Spring Bean 的生命周期进行解析,详细说明各个扩展点,希望对大家有所帮助。