@TOC
内容
开场白
上一节中,分析了springboot监听器的初始化和调用的逻辑,本节接着上一节来分析,本节分析springboot环境上下文初始化源码。首先看看整体的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);
//准备springboot容器上下文环境对象
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;
}
接下来进入到 prepareEnvironment(listeners, applicationArguments);
这个方法内部,如下图:
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 创建环境上下文对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置环境上下文对象
configureEnvironment(environment, applicationArguments.getSourceArgs());
//调用监听器执行环境上下文已经准备好的事件
listeners.environmentPrepared(environment);
//绑定主类
bindToSpringApplication(environment);
//自定义环境上下文
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
这个方法传进来有两个参数,第一个参数是上面步骤初始化好的SpringApplicationRunListeners对象,里面包含了一个广播器和所有的ApplicationListener集合;第二个参数就是main方法参数args分装的对象。
这个方法的逻辑体所有步骤我都写了注释,里面最主要的就是前三个步骤,其他步骤做的事情很少,而且对整个流程影响啊微乎其微。所以我主要解析前三个步骤。
1、创建环境上下文对象
2、配置环境上下文对象
3、调用监听器执行环境上下文已经准备好的事件
一、创建环境上下文对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
进入这个方法体内,可以看到如下代码:
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();
}
}
这个方法的逻辑其实比较简单,就是根据当前容器的类型决定new哪一个环境对象。假如我们启动的是servlet容器,那么这里就应该是实例化StandardServletEnvironment类型的实例,我们可以进入到这个类的实例化方法中,看看它有没有做什么工作:
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
public StandardServletEnvironment() {
}
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
propertySources.addLast(new StubPropertySource("servletContextInitParams"));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource("jndiProperties"));
}
super.customizePropertySources(propertySources);
}
public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(this.getPropertySources(), servletContext, servletConfig);
}
}
可以看到方法什么事都没有做,所以整个步骤只是推断该实例化哪一个环境对象,然后实例化返回。
二、配置环境上下文对象
configureEnvironment(environment, applicationArguments.getSourceArgs());
接下来我们进入到这个方法体内看看:
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
//配置属性
configurePropertySources(environment, args);
//配置配置文件
configureProfiles(environment, args);
}
说一下这两行代码分别要做什么:
1、第一行代码主要是把非配置文件中配置的一些key value键值对类型的配置放入到环境上下文中,非配置文件就是指不是从application.properties、application-${profile}.properties、或者apollo这种配置中心拉取的配置。
2、第二行主要是确定我们的profile激活的是哪个profile,profile就是类似于,dev、qa、prd这种用于区分环境的配置。
而这两行代码只是配置系统本身的一些配置或者通过代码硬编码方式编码进来的配置,对于配置文件的配置并不在这两个方法中进行。文件配置相关属性和配置后面会分析。
接下来进入到
configurePropertySources(environment, args);
这行代码方法体内:
protected void configurePropertySources(ConfigurableEnvironment environment,
String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(
new MapPropertySource("defaultProperties", this.defaultProperties));
}
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));
}
}
}
defaultProperties主要指通过编码方式设置进来的key value属性简直对。
addCommandLineProperties 就是通过main方法启动参数设置进来的属性。
一般来说我们的程序这两个都不会走,这个方法其实啥都不会执行。
但是如果你要通过这两种方式来添加属性,这里是会执行的,这里的逻辑也不是特别复杂,并且在我们平时生产中很少用到这里的代码,我就不再分析了。
MutablePropertySources这个类的数据结构很重要,因为后面的属性都是要放到这里面的。所以我们来看看它的源码:
public class MutablePropertySources implements PropertySources {
private final Log logger;
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
/**
* Create a new {@link MutablePropertySources} object.
*/
public MutablePropertySources() {
this.logger = LogFactory.getLog(getClass());
}
/**
* Create a new {@code MutablePropertySources} from the given propertySources
* object, preserving the original order of contained {@code PropertySource} objects.
*/
public MutablePropertySources(PropertySources propertySources) {
this();
for (PropertySource<?> propertySource : propertySources) {
addLast(propertySource);
}
}
/**
* Create a new {@link MutablePropertySources} object and inherit the given logger,
* usually from an enclosing {@link Environment}.
*/
MutablePropertySources(Log logger) {
this.logger = logger;
}
@Override
public Iterator<PropertySource<?>> iterator() {
return this.propertySourceList.iterator();
}
@Override
public boolean contains(String name) {
return this.propertySourceList.contains(PropertySource.named(name));
}
@Override
@Nullable
public PropertySource<?> get(String name) {
int index = this.propertySourceList.indexOf(PropertySource.named(name));
return (index != -1 ? this.propertySourceList.get(index) : null);
}
/**
* Add the given property source object with highest precedence.
*/
public void addFirst(PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Adding PropertySource '" + propertySource.getName() + "' with highest search precedence");
}
removeIfPresent(propertySource);
this.propertySourceList.add(0, propertySource);
}
/**
* Add the given property source object with lowest precedence.
*/
public void addLast(PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Adding PropertySource '" + propertySource.getName() + "' with lowest search precedence");
}
removeIfPresent(propertySource);
this.propertySourceList.add(propertySource);
}
/**
* Add the given property source object with precedence immediately higher
* than the named relative property source.
*/
public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Adding PropertySource '" + propertySource.getName() +
"' with search precedence immediately higher than '" + relativePropertySourceName + "'");
}
assertLegalRelativeAddition(relativePropertySourceName, propertySource);
removeIfPresent(propertySource);
int index = assertPresentAndGetIndex(relativePropertySourceName);
addAtIndex(index, propertySource);
}
/**
* Add the given property source object with precedence immediately lower
* than the named relative property source.
*/
public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Adding PropertySource '" + propertySource.getName() +
"' with search precedence immediately lower than '" + relativePropertySourceName + "'");
}
assertLegalRelativeAddition(relativePropertySourceName, propertySource);
removeIfPresent(propertySource);
int index = assertPresentAndGetIndex(relativePropertySourceName);
addAtIndex(index + 1, propertySource);
}
/**
* Return the precedence of the given property source, {@code -1} if not found.
*/
public int precedenceOf(PropertySource<?> propertySource) {
return this.propertySourceList.indexOf(propertySource);
}
/**
* Remove and return the property source with the given name, {@code null} if not found.
* @param name the name of the property source to find and remove
*/
@Nullable
public PropertySource<?> remove(String name) {
if (logger.isDebugEnabled()) {
logger.debug("Removing PropertySource '" + name + "'");
}
int index = this.propertySourceList.indexOf(PropertySource.named(name));
return (index != -1 ? this.propertySourceList.remove(index) : null);
}
/**
* Replace the property source with the given name with the given property source object.
* @param name the name of the property source to find and replace
* @param propertySource the replacement property source
* @throws IllegalArgumentException if no property source with the given name is present
* @see #contains
*/
public void replace(String name, PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Replacing PropertySource '" + name + "' with '" + propertySource.getName() + "'");
}
int index = assertPresentAndGetIndex(name);
this.propertySourceList.set(index, propertySource);
}
/**
* Return the number of {@link PropertySource} objects contained.
*/
public int size() {
return this.propertySourceList.size();
}
@Override
public String toString() {
return this.propertySourceList.toString();
}
/**
* Ensure that the given property source is not being added relative to itself.
*/
protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource<?> propertySource) {
String newPropertySourceName = propertySource.getName();
if (relativePropertySourceName.equals(newPropertySourceName)) {
throw new IllegalArgumentException(
"PropertySource named '" + newPropertySourceName + "' cannot be added relative to itself");
}
}
/**
* Remove the given property source if it is present.
*/
protected void removeIfPresent(PropertySource<?> propertySource) {
this.propertySourceList.remove(propertySource);
}
/**
* Add the given property source at a particular index in the list.
*/
private void addAtIndex(int index, PropertySource<?> propertySource) {
removeIfPresent(propertySource);
this.propertySourceList.add(index, propertySource);
}
/**
* Assert that the named property source is present and return its index.
* @param name {@linkplain PropertySource#getName() name of the property source} to find
* @throws IllegalArgumentException if the named property source is not present
*/
private int assertPresentAndGetIndex(String name) {
int index = this.propertySourceList.indexOf(PropertySource.named(name));
if (index == -1) {
throw new IllegalArgumentException("PropertySource named '" + name + "' does not exist");
}
return index;
}
}
它里面包含了一个CopyOnWriteArrayList属性,这个list是用于多线程并发读写的,使用其实和ArrayList差不多,就不展开分析了,我们来一起分析一下这个MutablePropertySources类的重要的几个方法:
(1) 实例化方法:
public MutablePropertySources(PropertySources propertySources) {
this();
for (PropertySource<?> propertySource : propertySources) {
addLast(propertySource);
}
}
(2) get方法:
@Override
@Nullable
public PropertySource<?> get(String name) {
int index = this.propertySourceList.indexOf(PropertySource.named(name));
return (index != -1 ? this.propertySourceList.get(index) : null);
}
(3)添加方法(只以addFirst为例)
public void addFirst(PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Adding PropertySource '" + propertySource.getName() + "' with highest search precedence");
}
removeIfPresent(propertySource);
this.propertySourceList.add(0, propertySource);
}
(4) 移除方法:
@Nullable
public PropertySource<?> remove(String name) {
if (logger.isDebugEnabled()) {
logger.debug("Removing PropertySource '" + name + "'");
}
int index = this.propertySourceList.indexOf(PropertySource.named(name));
return (index != -1 ? this.propertySourceList.remove(index) : null);
}
(5)替换方法:
public void replace(String name, PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Replacing PropertySource '" + name + "' with '" + propertySource.getName() + "'");
}
int index = assertPresentAndGetIndex(name);
this.propertySourceList.set(index, propertySource);
}
可以看出来,这些方法加起来几乎实现了一个map的功能,属性的名字就是key,属性的值就是value。
那spring为什么不直接用map 呢?
如果用map,里面的source也是map呢?那这个取值是不是就很麻烦了?
再说,如果直接用map,我想要继承这个类实现一个功能强大一点儿的子类,怎么办?继承map吗?这就是spring考虑到的扩展性。
属性说完,我们回到configureProfiles(environment, args);
配置这行代码来,进入这个方法体:
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
//执行这行代码是为了确定一些初始化
environment.getActiveProfiles();
// 添加通过硬编码方式加进来的profiles
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
// 从属性中获取profile
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
// 把新获取到的profile 添加到环境上下文中
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
着重分析一下environment.getActiveProfiles()
这个方法:
@Override
public String[] getActiveProfiles() {
return StringUtils.toStringArray(doGetActiveProfiles());
}
protected Set<String> doGetActiveProfiles() {
// 多线程并发控制
synchronized (this.activeProfiles) {
//只要我们没有通过硬编码方式添加profile这里就是空
if (this.activeProfiles.isEmpty()) {
//从环境上下文对象中获取名称为‘spring.profiles.active’这个的属性值
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
//从启动参数中解析出profile
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
至此,prepareEnvironment
这个方法的第二个步骤就解析完了,总结一下前两个步骤都干了些啥:
1、根据容器类型推断上下文类型并实例化上下文对象
2、把通过硬编码或者启动参数传进来的属性键值对假如到环境上下稳重
3、把通过硬编码或者启动参数传入的profile加入到环境上下文中
三、调用监听器执行环境上下文已经准备好的事件
监听器的执行机制和原理,这里就不再分析了,前面都做了详细的分析。这里主要是看listeners.environmentPrepared(environment);
这行代码都把环境上下文准备好了这个事件广播给了哪些监听器,哪些监听器本次事件执行了自己的逻辑,这些逻辑都做了哪些操作。
我在启动容器的时候通过debug打了一个断点,看看都有哪些listener接收了这个事件并执行了逻辑。
我们把断点打在类SimpleApplicationEventMulticaster的doInvokeListener方法的listener.onApplicationEvent(event)这行代码。
1、第一个进入断点处的是ConfigFileApplicationListener,这个类很重要,是实现配置文件配置的核心类,留到下一节来详细分析吧
2、第二个进入断点处的是AnsiOutputApplicationListener
进入AnsiOutputApplicationListener的onApplicationEvent方法:
@Override
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));
}
如果你的终端支持ANSI,设置彩色输出会让日志更具可读性。
这个和主流程关系不大,就不详细分析了
3、第三个是LoggingApplicationListener
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationStartingEvent) {
onApplicationStartingEvent((ApplicationStartingEvent) event);
}
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
.getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
}
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
protected void initialize(ConfigurableEnvironment environment,
ClassLoader classLoader) {
new LoggingSystemProperties(environment).apply();
LogFile logFile = LogFile.get(environment);
if (logFile != null) {
logFile.applyToSystemProperties();
}
initializeEarlyLoggingLevel(environment);
initializeSystem(environment, this.loggingSystem, logFile);
initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
这个listener是跟日志系统相关的,初始化日志系统,然后执行日志系统的第一步初始化。所以和主流程关系也不是很大,就不详细分析了,如果读者有兴趣可以去自行读相关源码,相关源码也不是很复杂。
4、第四个listener是ClasspathLoggingApplicationListener
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (logger.isDebugEnabled()) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
logger.debug("Application started with classpath: " + getClasspath());
}
else if (event instanceof ApplicationFailedEvent) {
logger.debug(
"Application failed to start with classpath: " + getClasspath());
}
}
}
主要就是打印日志。
5、第五个是DelegatingApplicationListener
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
if (delegates.isEmpty()) {
return;
}
this.multicaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<ApplicationEvent> listener : delegates) {
this.multicaster.addApplicationListener(listener);
}
}
if (this.multicaster != null) {
this.multicaster.multicastEvent(event);
}
}
从这个方法的代码可以整体看出来,就是从一个地方获取一个ApplicationListenner集合然后放到广播器中,我们进入到getListeners方法看看,这些listener都是从哪里来的:
private List<ApplicationListener<ApplicationEvent>> getListeners(
ConfigurableEnvironment environment) {
if (environment == null) {
return Collections.emptyList();
}
// 从环境上下文中获取key为'context.listener.classes'的属性值
String classNames = environment.getProperty(PROPERTY_NAME);
List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList<>();
如果属性值不为空
if (StringUtils.hasLength(classNames)) {
for (String className :
// 通过逗号把类名称截取开
StringUtils.commaDelimitedListToSet(classNames)) {
try {
//加载每个类
Class<?> clazz = ClassUtils.forName(className,
ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationListener.class, clazz, "class ["
+ className + "] must implement ApplicationListener");
//实例化每个类并加入到listeners集合中
listeners.add((ApplicationListener<ApplicationEvent>) BeanUtils
.instantiateClass(clazz));
}
catch (Exception ex) {
throw new ApplicationContextException(
"Failed to load context listener class [" + className + "]",
ex);
}
}
}
//排序
AnnotationAwareOrderComparator.sort(listeners);
return listeners;
}
总的来说就是把我们在properties或者各种配置中心中配置的ApplicationListener都实例化并加入到广播器中
6、第六个是FileEncodingApplicationListener
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (!environment.containsProperty("spring.mandatory-file-encoding")) {
return;
}
String encoding = System.getProperty("file.encoding");
String desired = environment.getProperty("spring.mandatory-file-encoding");
if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
logger.error("System property 'file.encoding' is currently '" + encoding
+ "'. It should be '" + desired
+ "' (as defined in 'spring.mandatoryFileEncoding').");
logger.error("Environment variable LANG is '" + System.getenv("LANG")
+ "'. You could use a locale setting that matches encoding='"
+ desired + "'.");
logger.error("Environment variable LC_ALL is '" + System.getenv("LC_ALL")
+ "'. You could use a locale setting that matches encoding='"
+ desired + "'.");
throw new IllegalStateException(
"The Java Virtual Machine has not been configured to use the "
+ "desired default character encoding (" + desired + ").");
}
}
这个方法代码太简单了,都不用解释了
至此,在这个阶段,所有的ApplicationListener都全部执行完了。
总结
今天我解析了prepareEnvironment
这个方法,总的来说这个方法主要就是准备应用上下文配置环境对象,通过三个步骤把我们所有的配置属性和profile全部加入到这个对象中。主要分以下这些步骤:
1、推断并创建环境上下文对象
2、把硬编码或者启动参数中的属性键值对全部加入到环境上下文对象中
3、把通过硬编码或者启动参数中的profile全部加入到环境上下文对象中
4、通过广播环境上下文对象准备好事件触发监听器,其中有一个监听器会执行很多配置文件属性读取并加入到环境上下文中的工作,这个具体源码我会在下节分析
5、还有一个监听器是把配置文件中配置的监听器类加入到广播器中
6、其他监听器做了一些打印日志,配置一些额外的工作,和主流程关系不大。