??前面给大家介绍了SpringBoot启动的核心流程,本文开始给大家详细的来介绍SpringBoot启动中的具体实现的相关细节。
SpringApplication构造器
??首先我们来看下在SpringApplication的构造方法中是如何帮我们完成这4个核心操作的。
@SuppressWarnings({ "unchecked", "rawtypes" })
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));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
1.webApplicationType
??首先来看下webApplicationType是如何来推导出当前启动的项目的类型。通过代码可以看到是通过deduceFromClassPath()方法根据ClassPath来推导出来的。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
??跟踪进去看代码
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
??在看整体的实现逻辑之前,我们先分别看两个内容,第一就是在上面的代码中使用到了相关的静态变量。
??这些静态变量其实就是一些绑定的Java类的全类路径。第二个就是 ClassUtils.isPresent() 方法,该方法的逻辑也非常简单,就是通过反射的方式获取对应的类型的Class对象,如果存在返回true,否则返回false
??所以到此推导的逻辑就非常清楚了
2.setInitializers
??然后我们再来看下如何实现加载初始化器的。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
??首先所有的初始化器都实现了 ApplicationContextInitializer 接口,也就是根据这个类型来加载相关的实现类。
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C var1);
}
??然后加载的关键方法是 getSpringFactoriesInstances() 方法。该方法会加载 spring.factories 文件中的key为 org.springframework.context.ApplicationContextInitializer 的值。
spring-boot项目下
# 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.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
spring-boot-autoconfigure项目下
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
??具体的加载方法为 getSpringFacotiesInstance() 方法,我们进入查看
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
??先进入 SpringFactoriesLoader.loadFactoryNames(type, classLoader) 中具体查看加载文件的过程.
??然后我们来看下 loadSpringFactories 方法
??通过Debug的方式查看会更清楚哦
??通过 loadSpringFactories 方法我们看到把 spring.factories 文件中的所有信息都加载到了内存中了,但是我们现在只需要加载 ApplicationContextInitializer 类型的数据。这时我们再通过 getOrDefault() 方法来查看。
??进入方法中查看
??然后会根据反射获取对应的实例对象。
??好了到这其实我们就清楚了 getSpringFactoriesInstances 方法的作用就是帮我们获取定义在 META-INF/spring.factories 文件中的可以为 ApplicationContextInitializer 的值。并通过反射的方式获取实例对象。然后把实例的对象信息存储在了SpringApplication的 initializers 属性中。
3.setListeners
??清楚了 setInitializers() 方法的作用后,再看 setListeners() 方法就非常简单了,都是调用了 getSpringFactoriesInstances 方法,只是传入的类型不同。也就是要获取的 META-INF/spring.factories 文件中定义的不同信息罢了。
??即加载定义在 META-INF/spring.factories 文件中声明的所有的监听器,并将获取后的监听器存储在了 SpringApplication 的 listeners 属性中。
??默认加载的监听器为:
4.mainApplicationClass
??最后我们来看下 duduceMainApplicaitonClass() 方法是如何反推导出main方法所在的Class对象的。通过源码我们可以看到是通过 StackTrace 来实现的。
StackTrace:
我们在学习函数调用时,都知道每个函数都拥有自己的栈空间。
一个函数被调用时,就创建一个新的栈空间。那么通过函数的嵌套调用最后就形成了一个函数调用堆栈
??StackTrace 其实就是记录了程序方法执行的链路。通过Debug方式可以更直观的来呈现。
??那么相关的调用链路我们都可以获取到,剩下的就只需要获取每链路判断执行的方法名称是否是 main 就可以了。
??好了到此相关的4个核心步骤就给大家分析完了,希望对大家能有所帮助哦!
|