spring boot 加载过程分析(一)
本文源码基于spring boot 2.0.3 f版,该版本引入了web-flux模块,这里先分析servlet类型的启动过程。后续有时间再了解reactive相关的。
spring boot的入口是通过一个main方法开启,调用SpringApplication的静态方法run来首先实例化该类,看一下该类的构造方法。
SpringApplication的实例化
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 探测当前是否为web环境
this.webApplicationType = deduceWebApplicationType();
// 收集ApplicationContextInitializer的实例
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 收集ApplicationListener的实例
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 启动main的类对象
this.mainApplicationClass = deduceMainApplicationClass();
}
复制代码
1. deduceWebApplicationType()
复制代码
根据classpath中查找相关的web类(servelt)来判断当前是否为web环境,且在2.0.0+版中加入了区分reactive、servlet类型.
2. setInitializers与setListeners
复制代码
收集两种类型对应的实例,主要通过getSpringFactoriesInstances方法来查找对应的实例,下面仔细分析一下该方法
3. getSpringFactoriesInstances
复制代码
首先会加载系统下的META-INF/spring.factories文件内容,加载对应类全限定名,然后将传入的Class参数作为key,去找到对应的列表,最后将查找到的类进行实例化操作并返回。 ps: 可关注一下该方法实现,后面还会使用到
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 根据类型查找对应的类全限定名列表
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 实例化找出来的类
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
... 省略部分代码...
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION):
classLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
... 省略部分代码...
}
复制代码
通过上面步骤,完成ApplicationContextInitialize、ApplicationListener对应类的实例化操作并收集到当前对象的两个list集合中。
SpringApplication对象的run方法
该方法返回一个ConfigurableApplicationContext类型的对象。方法较复杂,咱们分解开一点一点来分析。
public ConfigurableApplicationContext run(String... args) {
...省略代码...
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
...省略代码...
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);
...省略代码...
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;
}
复制代码
1. SpringApplicationRunListener
// 加载实例列表,返回包装对象
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发布启动事件
listeners.starting();
复制代码
同样使用getSpringFactoriesInstances来加载相应的类实例。
这里如果没有扩展自定义实现,实际只实例化了EventPublishingRunListener(启动监听的事件发布器); 该类内部有一个SimpleApplicationEventMulticaster事件广播器,负责对之前收集到的ApplicationListener类型实例所监听的事件广播出去。
2. ConfigurableEnvironment
prepareEnvironment 根据服务类型创建环境配置
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 创建并配置环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
复制代码
首先通过getOrCreateEnvironment()创建ConfigurableEnvironment对象,因为我们servlet环境,所以创建StandardServletEnvironment对象。跟踪代码可发现,会在AbstractEnvironment中有个集合保存profiles,有个propertySources保存properties(会通过子类增加一些占位符字段),同时定义了一个property解析器PropertySourcesPropertyResolver。
// 记录spring.profiles.active的值
private final Set<String> activeProfiles = new LinkedHashSet<>();
// 记录spring.profiles.default的值
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
// 存储propertySource的对象
private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);
// propertySource的解析器
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
public AbstractEnvironment() {
// 通过子类设置部分占位符字段
customizePropertySources(this.propertySources);
}
复制代码
然后调用configurePropertySources配置properties,调用configureProfiles配置profiles。根据addCommandLineProperties,args来判断是否有命令行参数,如果有则加入到最前面(优先级最高)。
最后,在环境配置完成后触发监听器的环境配置事件。
listeners.environmentPrepared(environment);
这里后面新加了个方法,留待后面再分析
bindToSpringApplication(environment) // todo
3. printBanner
打印启动的banner,根据环境配置的模式打印到log或console等,这里不再分析。
分析到这里,整个启动过程还没正式开始,接下来的createApplicationContext()方法则是ApplicatonContext的创建过程,也是spring容器的初始化的过程;下一篇着重来分析该方法的执行过程。