文章目录
- spring流程概览
- new SpringApplication()
- setInitializers() 和 setListeners()
- run()
- prepareEnvironment()
- createApplicationContext()
- prepareContext()
- refreshContext ()
- 总结
spring流程概览
这篇文章主要聊聊spring启动流程经历了哪些步骤,以及它们做了什么。
一般我们都是选择使用SpringApplication.run()这个静态方法来启动spring。
还是一样我们先大体上看下它做了什么
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
可以看出这里分成了两步,
第一步:创建了一个SpringApplication对象
第二步:访问其run方法
那我们就先来看看创建SpringApplication对象做了什么。
new 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));
this.webApplicationType = WebApplicationType.deduceFromClasspath(); //根据加载的类 推测出应用的类型,这个属性就决定了容器的类型
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //初始化一些ApplicationContextInitializer类
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//初始化一些ApplicationListener
this.mainApplicationClass = deduceMainApplicationClass();//推测应用入口
}
对于我们来说比较重要的就是setInitializers方法和setListeners方法,这两个位置使我们可以自定的一个切入口。
那么下面我们就来看看 他是如何寻找ApplicationContextInitializer和ApplicationListener的。
setInitializers() 和 setListeners()
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//通过类名称 找到实现的限定名
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//通过实现的限定名实例化对象
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
还是分为两步,第一步找到传入的类名称(例如ApplicationContextInitializer)的所有实现的限定类名。 第二步通过全限定名实例化成对象
那么它是如何找到实现了type的限定类名呢?
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
...
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
...
return result;
}
通过加载FACTORIES_RESOURCE_LOCATION 这个路径下的文件
那么FACTORIES_RESOURCE_LOCATION 的值是多少呢?
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
文件就是这样,截取springboot的一部分配置信息
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
好了,到这里限定类名如何获取我们已经知道,接下来就是看如何实例化了。
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);//通过限定类名 获取Class对象
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); //获取指定入参个数的构造方法
T instance = (T) BeanUtils.instantiateClass(constructor, args); //通过参入的参数列表和构造器 构造实例
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
分为三步,第一步通过限定类名获取到Class对象,第二步获取构造方法,第三步调用构造方法实例对象。
到这里创建SpringApplication的一些工作我们已经了解完了,我们也知道如果添加自定义的ApplicationContextInitializer和ApplicationListener了。
下面我们就看看,启动spring容器又做了什么,以及ApplicationContextInitializer和ApplicationListener都分别在哪些时间节点触发,中途是否又有其他的扩展点提供给我们使用。
run()
由于这段代码比较长,我先贴一部分经过我删减的流程,主要是为了使我们更聚焦,这样我们更容易从宏观上去把控整个流程。后面一些文章会分析其中的细节。
public ConfigurableApplicationContext run(String... args) {
...
//创建一个事件广播器
SpringApplicationRunListeners listeners = getRunListeners(args);
//发布一个启动广播
listeners.starting();
...
//准备环境,装载一些PropertySources
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//创建容器,由前面没聊到的webApplicationType 决定
context = createApplicationContext();
//很眼熟把,加载SpringBootExceptionReporter(这个主要是处理异常)
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备容器
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新容器
refreshContext(context);
...
//发布一个启动完毕的广播
listeners.started(context);
...
return context;
}
中间有些步骤已经被我省略了,因为那部分我们很难做出改变。 现在来看这个启动步骤就变得特别清晰了。
- 准备环境
- 创建容器
- 准备容器
- 刷新容器
搞清楚了它的主体流程,我们接下来就看看它们分别又做了什么。
prepareEnvironment()
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//创建环境容器,总共分为三类, 一类非Web,一类Web容器,一类内嵌型web容器
ConfigurableEnvironment environment = getOrCreateEnvironment();
//环境属性的加载,默认的properties,profile
configureEnvironment(environment, applicationArguments.getSourceArgs());
//发送广播 ApplicationEnvironmentPreparedEvent 事件
listeners.environmentPrepared(environment);
//将前面的properties属性注入到this中
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
//给我的感觉像是,在sources的头部放了一个整合了所有sources的整合包?
ConfigurationPropertySources.attach(environment);
return environment;
}
还是分步走,
- 第一步创建一个环境、
- 环境属性的加载,将启动参数和defaultProperties属性封装PropertySources对象,其中启动参数排在第一个使用的优先级最高,defaultProperties排在最后一个也就是使用的优先级最低。
- 广播enviromentPrepared事件
- 将现有的PropertySources对象中前缀为"spring.main"的属性注入当前springApplication中
就此环境容器已经准备完毕了
createApplicationContext()
接下来就是创建容器了,
这个比较简单就是根据上面webApplicationType找到对应的限定类名,然后实例化一个容器
prepareContext()
这里面涉及到一个重要的方法,前面我们提到过如何去加载一个ApplicationContextInitializer,而使用,就是在prepareContext里面
private void prepareContext(...) {
...
//访问ApplicationContextInitialized
applyInitializers(context);
//对事件ApplicationContextInitializedEvent进行广播
listeners.contextPrepared(context);
...
//进行广播
listeners.contextLoaded(context);
}
applyInitializers()就是遍历访问spring加载进来的所有ApplicationContextInitializer的实现,并调用他们的initialize方法。
refreshContext ()
刷新容器,这个就是spring启动的重头戏,里面的流程非常多,而且有很多我们对于spring感性的认知,在里面也有了详细的描述,由于篇幅问题,我将会把它独立成一个单独的章节。
这里先笼统的说下它的功能,包括了 bean前置的一些处理,bean内部属性的处理,bean自身的处理,以及非常多的扩展的处理等等。
总结
spring本身是一个非常庞当的项目,可能上述有一些重要的流程被我忽略掉了,如果有错误的地方欢迎读者指正。
与君共勉。