springboot启动源码解析(四):初始化默认应用参数、准备启动环境

此章节主要对springboot启动过程中,发生的【初始化应用参数】、【准备启动环境】进行源码解析,对应的代码如图1所示:

图1:

// 初始化应用参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备启动环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 配置需要忽略的Bean信息
configureIgnoreBeanInfo(environment);
// 打印Banner
Banner printedBanner = printBanner(environment);

初始化应用参数

对启动时的参数进行解析,例如:java -jar --spring.profiles.active=prod等


准备启动环境

图2:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
	// 1、获取或创建一个应用环境
	ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 2、对应用环境进行配置(主要为配置Formatters和Converters、命令行参数、profiles等)
	configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 3、使配置的环境参数与环境匹配
	ConfigurationPropertySources.attach(environment);
        // 4、向监听器发送环境准备事件
	listeners.environmentPrepared(bootstrapContext, environment);
        // 5、将DefaultProperties移动到列表最后
	DefaultPropertiesPropertySource.moveToEnd(environment);
        // 6、设置环境中activeProfiles
	configureAdditionalProfiles(environment);
        // 7、将environment中以spring.main开头的source绑定到springApplication中
	bindToSpringApplication(environment);
        // 8、将环境转换为与application能够匹配的类型
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
        // 9、使配置的环境参数与环境匹配
	ConfigurationPropertySources.attach(environment);
	return environment;
}

在启动过程中,通过调用prepareEnvironment方法生成了springboot启动时环境,environment管理了springboot需要的配置资源信息、Formatters、Converters、和配置文件中用户的自定义配置等。

如图2所示,准备环境共经历了9个步骤,接下来对这些步骤进行解析。

1、获取或创建一个应用环境

图3:

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

通过调用【getOrCreateEnvironment】方法获取一个环境。如图3所示,由于当前的springApplication.environment为null,且webApplicationType为SERVLET,故返回StandardServletEnvironment实例对象

注:

① 在StandardServletEnvironment的父类AbstractEnvironment的构造方法中,调用了customizePropertySources(this.propertySources)方法,对一些属性资源进行了初始化,故StandardServletEnvironment对象的propertySources中包含了以下四个元素:servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment。

② 在StandardServletEnvironment的父类AbstractEnvironment的成员变量propertyResolver,类型为PropertySourcesPropertyResolver,它的父类AbstractPropertyResolver中定义了"${}"占位符,用于后续解析配置文件时使用

 

2、对应用环境进行配置

图4:

private boolean addConversionService = true;

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
	// this.addConversionService默认为true
        if (this.addConversionService) {
                // 获取一个共享的默认应用程序转换服务实例,并将该实例配置到环境的conversionService成员变量中
		ConversionService conversionService = ApplicationConversionService.getSharedInstance();
		environment.setConversionService((ConfigurableConversionService) conversionService);
	}
        // 配置PropertySources
	configurePropertySources(environment, args);
        // 配置Profiles
	configureProfiles(environment, args);
}

通过调用【configureEnvironment】方法对应用环境进行配置,在此步骤中主要配置了环境中的Converters、Formatters、命令行参数、profiles等。如图4所示,配置应用环境共进行了3步操作,分别为:

① 首先通过调用ApplicationConversionService.getSharedInstance获取到一个共享的默认应用程序转换服务实例,通过图5可以发现,该实例是通过懒加载返回的一个ApplicationConversionService单例。

在ApplicationConversionService的构造方法中,通过调用configure(this)加入了一些默认的Converters和Formatters(图6)。

图5(getSharedInstance):

public static ConversionService getSharedInstance() {
	ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
	if (sharedInstance == null) {
		synchronized (ApplicationConversionService.class) {
			sharedInstance = ApplicationConversionService.sharedInstance;
			if (sharedInstance == null) {
				sharedInstance = new ApplicationConversionService();
				ApplicationConversionService.sharedInstance = sharedInstance;
			}
		}
	}
	return sharedInstance;
}

 图6(ApplicationConversionService构造方法):

public ApplicationConversionService() {
	this(null);
}

public ApplicationConversionService(StringValueResolver embeddedValueResolver) {
	if (embeddedValueResolver != null) {
		setEmbeddedValueResolver(embeddedValueResolver);
	}
	configure(this);
}

/**
 * 向registry中加入converters和formatters
 */
public static void configure(FormatterRegistry registry) {
	DefaultConversionService.addDefaultConverters(registry);
	DefaultFormattingConversionService.addDefaultFormatters(registry);
	addApplicationFormatters(registry);
	addApplicationConverters(registry);
}

② 设置命令行参数资源,如图7所示,由于defaultProperties为null,故不将defaultProperties加入到sources列表后。若存在命令行参数,则将args转化为属性资源配置加入到sources的开头。

图7(configurePropertySources):

private boolean addCommandLineProperties = true;

public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs";

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
	MutablePropertySources sources = environment.getPropertySources();
        // 若this.defaultProperties不为空,则将this.defaultProperties中的元素加入到sources最后
	DefaultPropertiesPropertySource.ifNotEmpty(this.defaultProperties, sources::addLast);
        // 若this.addCommandLineProperties和args.length>0同为true,则将命令行参数加入到环境属性资源中
	if (this.addCommandLineProperties && args.length > 0) {
		String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
		if (sources.contains(name)) {
			PropertySource<?> source = sources.get(name);
			CompositePropertySource composite = new CompositePropertySource(name);
			composite.addPropertySource(
					new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
			composite.addPropertySource(source);
			sources.replace(name, composite);
		}
		else {
			sources.addFirst(new SimpleCommandLinePropertySource(args));
		}
	}
}

③ 设置配置文件:由于方法体为空,故此步骤不执行任何操作,应该是留给未来的springboot进行扩展使用。

图8(configureProfiles):

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
}

3、使配置的环境参数与环境匹配

图9(ConfigurationPropertySources.attach):

private static final String ATTACHED_PROPERTY_SOURCE_NAME = "configurationProperties";

public static void attach(Environment environment) {
	Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
	MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
	PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
	if (attached != null && attached.getSource() != sources) {
		sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
		attached = null;
	}
	if (attached == null) {
                // 生成一个新的ConfigurationPropertySourcesPropertySource,并将它放入到sources的第一个
		sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
				new SpringConfigurationPropertySources(sources)));
	}
}

该步骤的目的是将ConfigurationPropertySource支持固定到environment中,以便于PropertySourcesPropertyResolver使用配置属性名称进行解析。

图9所示,首先获取到环境的sources,接下来判断sources中是否包含名称为"configurationProperties"的资源,如果有则移除。最后向sources中添加一个新的"配置属性资源"属性资源,将sources中的每个属性资源变为ConfigurationPropertySource(Adapts each PropertySource managed by the environment to a ConfigurationPropertySource and allows classic PropertySourcesPropertyResolver calls to resolve using configuration property names)

4、向监听器发送环境准备事件

图10(listeners的environmentPrepared方法):

void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
    doWithListeners("spring.boot.application.environment-prepared",
            (listener) -> listener.environmentPrepared(bootstrapContext, environment));
}

图11(EventPublishingRunListener的environmentPrepared方法):

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

图10图11所示,listeners会让维护的EventPublishingRunListener会执行environmentPrepared方法,EventPublishingRunListener通过调用initialMulticaster的multicastEvent方法,从维护的9个监听器中找到匹配环境准备事件监听器,遍历它们执行invokeListener方法。(事件发布详见:springboot启动源码解析(三):初始化启动上下文、初始化监听器列表、发布开始启动事件)。

在EventPublishingRunListener的initialMulticaster里维护的9个监听器中,environmentPreparedEvent共匹配到了6个监听器,它们分别为:EnvironmentPostProcessorApplicationListener、AnsiOutputApplicationListener、LoggingApplicationListener、BackgroundPreinitializer、DelegatingApplicationListener、FileEncodingApplicationListener,它们执行invokeListener的解析入下:

① EnvironmentPostProcessorApplicationListener:(未完待续~)

② AnsiOutputApplicationListener:设置ansi编码格式(图12

图12(AnsiOutputApplicationListener.onApplicationEvent):

/**
 * 设置ansi编码
 */
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
	ConfigurableEnvironment environment = event.getEnvironment();
	Binder.get(environment).bind("spring.output.ansi.enabled", AnsiOutput.Enabled.class)
			.ifBound(AnsiOutput::setEnabled);
	AnsiOutput.setConsoleAvailable(environment.getProperty("spring.output.ansi.console-available", Boolean.class));
}

③ LoggingApplicationListener:根据通过环境和类路径表示的首选项初始化日志记录系统(图13)。

图13(LoggingApplicationListener.onApplicationEnvironmentPreparedEvent):

/**
 * 根据通过环境和类路径表示的首选项初始化日志记录系统。
 */
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
	getLoggingSystemProperties(environment).apply();
	this.logFile = LogFile.get(environment);
	if (this.logFile != null) {
		this.logFile.applyToSystemProperties();
	}
	this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
	initializeEarlyLoggingLevel(environment);
	initializeSystem(environment, this.loggingSystem, this.logFile);
	initializeFinalLoggingLevels(environment, this.loggingSystem);
	registerShutdownHookIfNecessary(environment, this.loggingSystem);
}

④ BackgroundPreinitializer:初始化一些转换器、解析器,包括了:ConversionServiceInitializer、ValidationInitializer、MessageConverterInitializer、JacksonInitializer、CharsetInitializer。如图10所示,会新启线程执行初始化方法。每个Initializer都实现了Runnable接口,故每个Initializer都会启用新的线程去初始化,在启动完这些Initializer前,会使用preinitializationComplete(CountDownLatch)阻塞住主线程(图14)。

图14(BackgroundPreinitializer.performPreinitialization):

/**
* 初始化一些转换器、解析器
*/
private void performPreinitialization() {
	try {
		Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				runSafely(new ConversionServiceInitializer());
				runSafely(new ValidationInitializer());
				runSafely(new MessageConverterInitializer());
				runSafely(new JacksonInitializer());
				runSafely(new CharsetInitializer());
				preinitializationComplete.countDown();
			}

			public void runSafely(Runnable runnable) {
				try {
					runnable.run();
				} catch (Throwable ex) {
					// Ignore
				}
			}
		}, "background-preinit");
		thread.start();
	} catch (Exception ex) {
		preinitializationComplete.countDown();
	}
}

⑤ DelegatingApplicationListener:未执行任何操作

⑥ FileEncodingApplicationListener:未执行任何操作

未完待续~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值