有道无术,术尚可求,有术无道,止于术。
本系列Spring Boot版本2.7.0
在上文中,我们分析到启动刚开始阶段,会去调用监听器进行一些初始化的操作,接下来进入到环境准备阶段:
// 封装参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 配置 beaninfo
this.configureIgnoreBeanInfo(environment);
// 打印 Banner
Banner printedBanner = this.printBanner(environment);
ApplicationArguments翻译过来是应用程序参数的意思,它的主要作用是提供对用于运行 SpringApplication 的参数的访问,也就是封装了我们在使用run方式时传入的参数,以便其他阶段访问使用。
SpringApplication.run(App1.class,args);
该接口的源码如下:
public interface ApplicationArguments {
// 返回传递给应用程序的原始未处理参数
String[] getSourceArgs();
// 返回所有选项参数的名称。
Set<String> getOptionNames();
// 返回从参数解析的选项参数集是否包含具有给定名称的选项。
boolean containsOption(String name);
// 返回与具有给定名称的参数选项关联的值的集合。
List<String> getOptionValues(String name);
// 返回已解析的非选项参数的集合。
List<String> getNonOptionArgs();
}
它只有一个默认的实现类DefaultApplicationArguments。
ConfigurableEnvironment是Spring 提供的接口,并继承了多个接口:

首先简单说明下它的父类接口的作用:
PropertyResolver多了一个类型转换。所以ConfigurableEnvironment 具备了属性解析器和当前应用环境的能力,而且在这个基础上扩展了很多自己的方法:
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
// 设置当前激活的 profile
void setActiveProfiles(String... profiles);
// 添加 profile
void addActiveProfile(String profile);
// 设置默认的 profile
void setDefaultProfiles(String... profiles);
// 获取PropertySource键值组合的集合
MutablePropertySources getPropertySources();
// 获取系统属性
Map<String, Object> getSystemProperties();
// 获取系统环境变量
Map<String, Object> getSystemEnvironment();
// 合并其他 ConfigurableEnvironment
void merge(ConfigurableEnvironment parent);
}
该接口的实现类中,可以看到根据不同类型的Web 应用创建不同的环境:

PropertySource翻译过来是属性源的意思,属性源就是封装了属性的任何类型对象,比如:
EnvironmentPostProcessor 接口也是Spring Boot 自己提供的接口,他的作用是在刷新应用程序上下文之前对Environment进行后置处理,它的实现类必须在 META-INFspring.factories文件中注册。
可以看到它只有一个方法,我们可以通过该方法,对ConfigurableEnvironment 进行自定义处理,比如加载自定义属性源。
@FunctionalInterface
public interface EnvironmentPostProcessor {
// 后置处理
void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}
这一步很简单,就是将run方式时传入的参数封装为ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
prepareEnvironment准备环境方法中,会完成环境的创建、配置:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 1. 创建环境
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
// 2. 配置环境
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
// 3. 连接
ConfigurationPropertySources.attach((Environment)environment);
// 4. 调用监听器
listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
// 5. 将 defaultProperties 移到最后位置
DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
this.bindToSpringApplication((ConfigurableEnvironment)environment);
// 6. 没有设置 isCustomEnvironment 属性,
if (!this.isCustomEnvironment) {
// 环境转换器
EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());
// 进行转换,这里实际已经是 Servlet 的类型的,所以不转换直接返回
environment = environmentConverter.convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
// 7. 再次连接并返回
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
准备环境阶段,第一步就是根据Web 应用的类型创建环境对象:
private ConfigurableEnvironment getOrCreateEnvironment() {
// 1. 环境已初始化,直接返回
if (this.environment != null) {
return this.environment;
} else {
// 2. 判断环境的类型
switch(this.webApplicationType) {
// 3. SERVLET类型=》ApplicationServletEnvironment
case SERVLET:
return new ApplicationServletEnvironment();
// REACTIVE类型=》ApplicationReactiveWebEnvironment
case REACTIVE:
return new ApplicationReactiveWebEnvironment();
default:
// 默认=》ApplicationEnvironment
return new ApplicationEnvironment();
}
}
}
在SpringApplication对象创建时,会去判断当前应用Web类型:
this.webApplicationType = WebApplicationType.deduceFromClasspath();
判断的逻辑是查看Classpath下是否有对应的类,因为这里使用的是Spring MVC,所以最后返回的是ApplicationServletEnvironment。
static WebApplicationType deduceFromClasspath() {
// org.springframework.web.reactive.DispatcherHandler
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 都不是,判断是 SERVLET
return WebApplicationType.SERVLET;
}
new ApplicationServletEnvironment()直接调用无参构造函数,实际是调用父类的无参构造函数:
public AbstractEnvironment() {
// 创建一个PropertySources 对象,用于存放所有的 PropertySource
this(new MutablePropertySources());
}
protected AbstractEnvironment(MutablePropertySources propertySources) {
this.logger = LogFactory.getLog(this.getClass());
this.activeProfiles = new LinkedHashSet(); // 激活的 Profile
this.defaultProfiles = new LinkedHashSet(this.getReservedDefaultProfiles()); // 默认的Profile =》 default
this.propertySources = propertySources;
// 创建属性解析器=》PropertySourcesPropertyResolver
this.propertyResolver = this.createPropertyResolver(propertySources);
// 自定义属性源
this.customizePropertySources(propertySources);
}
在customizePropertySources可以看到创建了三个属性源,名称分别为:
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
propertySources.addLast(new StubPropertySource("servletContextInitParams"));
if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource("jndiProperties"));
}
super.customizePropertySources(propertySources);
}
紧接着进入到customizePropertySources(propertySources)方法,也创建了systemProperties和systemEnvironment属性源。
protected void customizePropertySources(MutablePropertySources propertySources) {
// 添加System 系统属性
propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));
// 添加系统环境属性
propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
}
最终这一步创建的ConfigurableEnvironment对象中,一共有4个PropertySource,其中前两个是没有放任何东西的:

systemProperties中,是将System类中的属性获取过来并封装,可以看到这里主要是JDK 相关的属性:

systemEnvironment主要是当前操作系统的很多属性:

接着进入到配置环境方法中,会对属性、Profile 进行配置。
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
// 设置转换服务
if (this.addConversionService) {
environment.setConversionService(new ApplicationConversionService());
}
// 配置属性
this.configurePropertySources(environment, args);
// 配置 Profile
this.configureProfiles(environment, args);
}
configurePropertySources方法主要是将启动命令中的参数和run 方法中的参数封装为PropertySource。
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
// 获取所有的属性源
MutablePropertySources sources = environment.getPropertySources();
if (!CollectionUtils.isEmpty(this.defaultProperties)) {
DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
}
// 是否添加启动命令参数,默认True
if (this.addCommandLineProperties && args.length > 0) {
String name = "commandLineArgs";
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));
}
}
}
configureProfiles则是一个空的方法,没有实现,所以配置环境这一步主要也是获取其他方面的属性。
在attach方法中,主要是将环境中的 PropertySources 封装到一个名称为configurationProperties的PropertySource中。
public static void attach(Environment environment) {
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
// 1. 获取环境中的 PropertySources
MutablePropertySources sources = ((ConfigurableEnvironment)environment).getPropertySources();
// 2. 判断 是否有configurationProperties,之前加载的四个是没有这个名字的所以为NULL
PropertySource<?> attached = getAttached(sources);
// 3. 创建一个configurationProperties。
if (attached == null || !isUsingSources((PropertySource)attached, sources)) {
attached = new ConfigurationPropertySourcesPropertySource("configurationProperties", new SpringConfigurationPropertySources(sources));
}
// 4. 将configurationProperties 设置到第一位置
sources.remove("configurationProperties");
sources.addFirst((PropertySource)attached);
}
和上一篇开始启动阶段调用监听器一样,在准备环境阶段,也会调用监听器。
该阶段对应的事件类型为ApplicationEnvironmentPreparedEvent,支持该事件的监听器有5种:

首先进入的是EnvironmentPostProcessorApplicationListener,它的作用是用于触发在 spring.factories文件中声明的EnvironmentPostProcessor实例。
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
// 1. 当前环境和SpringApplication
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
// 2. SPI 获取环境后置处理器,并调用处理
Iterator var4 = this.getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext()).iterator();
while(var4.hasNext()) {
// 3. 调用后置处理器处理方法
EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var4.next();
postProcessor.postProcessEnvironment(environment, application);
}
}
一共有7个 环境后置处理器:

其中最重要的就是ConfigDataEnvironmentPostProcessor,就是用它将application.yml文件中的属性加载到环境中的,加载完成后可以看到你在application.yml文件中配置的所有属性:

其他的环境后置处理器的作用:
RandomValuePropertySource属性源,可以通过environment.getProperty("random.*")返回各种随机值。SystemEnvironmentPropertySource替换为一个新的对象OriginAwareSystemEnvironmentPropertySource。spring.application.json 或 SPRING_APPLICATION_JSON 配置的 json 字符串。Cloud Foundry开源PaaS云平台相关属性源。META-INF/spring.integration.properties中的属性映射为属性源。其他监听器在准备环境阶段的作用:
接着调用bindToSpringApplication,其作用是将spring.main开头的配置信息绑定到SpringApplication类对应的属性中。
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
} catch (Exception var3) {
throw new IllegalStateException("Cannot bind to SpringApplication", var3);
}
}
环境创建成功后,进入到configureIgnoreBeanInfo方法,就是配置了一个spring.beaninfo.ignore属性,默认为TRUE,跳过对 BeanInfo 的搜索,这个BeanInfo是JDK 声明的一个接口,可以使用Introspector.getBeanInfo获取一个对象的方法、属性、事件和其他功能的显式信息。
设置spring.beaninfo.ignore,将使用低级反射和应用标准设计模式自动分析某个类的信息,可以提升一些性能。
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
if (System.getProperty("spring.beaninfo.ignore") == null) {
Boolean ignore = (Boolean)environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
System.setProperty("spring.beaninfo.ignore", ignore.toString());
}
}
环境准备完成后,开始打印Banner :
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Mode.OFF) {
return null;
} else {
ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader((ClassLoader)null);
// 创建打印对象
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter((ResourceLoader)resourceLoader, this.banner);
// 根据配置模式,进行打印
return this.bannerMode == Mode.LOG ? bannerPrinter.print(environment, this.mainApplicationClass, logger) : bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
}
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
// 从环境中获取Banner 对象并打印
Banner banner = this.getBanner(environment);
banner.printBanner(environment, sourceClass, out);
return new SpringApplicationBannerPrinter.PrintedBanner(banner, sourceClass);
}
ApplicationServletEnvironment;ApplicationServletEnvironment调用父类的构造方法,创建JDK 和操作系统相关的属性源PropertySource;