此章节主要对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:未执行任何操作
未完待续~