前言
前面已经罗嗦了三篇【SpringBoot 四】【SpringBoot 五】【SpringBoot 六】博客用来介绍观察者模式的理论以及实现,都是为了本节讲解SpringBoot启动流程的源码做准备的,因为在阅读SpringBoot源码启动流程的时候发现spring公司的大神们在设计SpringBoot的时候,把观察者模式用到了极限。如果不把观察者模式讲清楚,那么读起来SpringBoot的源码会非常的痛苦。那么既然观察者模式已经搞清楚了,下面就开始正式进入SpringBoot的源码探究的过程。但是要说明一点,本篇探究的源码是基于SpringBoot.2.2.x,因为笔者决定编译阅读源码写笔记的时候,2.2.x是最新版。没想到短短半年就更新到了SpringBoot.2.4.x。关于更新的内容,笔者在后续新开一个博客做一个代码对比,但是总体的思路还是没有变。因此本篇对于SpringBoot源码探究的参考价值还是非常大的,尤其是想要读源码,面对庞大的SpringBoot不知从何下手的同学。更多Spring内容进入【Spring解读系列目录】。
SpringBoot启动入口
说到SpringBoot启动,相信大家都知道,只需要一个main()
方法作为程序的入口,运行起来SpringBoot就会把Spring容器初始化,Tomcat启动,配置Servlet等等一系列的工作都做完。所以探究源码的入口也要从run()方
法开始。
public class MyBootStartTest {
public static void main(String[] args) {
SpringApplication.run(MyBootStartTest.class);
}
}
run()方法
没啥可以说的,一路点进去。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
进入第二个run()方法以后,其实可以发现SpringBoot中的run()静态方法一共做了两件事件:1. new 一个SpringApplication的类。2. 执行SpringApplication类的run()方法。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
SpringApplication
首先分析SpringApplication(primarySources)
构造方法。
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
可以看到SpringApplication构造方法也被封装了一层,Spring的封装简直丧心病狂。
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();
//找出来ApplicationContextInitializer类,并实例化赋值
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//找出来ApplicationListener类,并实例化赋值
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断方法入口
this.mainApplicationClass = deduceMainApplicationClass();
}
this()点进去以后发现就四行代码值得分析,其实也就是这四行造就了一个强大的SpringBoot。先从第一行有用的代码开始看起,很多时候SpringBoot用作搭建一个web项目,但是SpringBoot如果不作为一个Web项目启动,就会加载不同的Spring组件,这个是怎么判断的呢?就是在this.webApplicationType = WebApplicationType.deduceFromClasspath()
里面做的项目类型的推断和区分,所以先来看SpringBoot如何推断一个项目的类型,也就是deduceFromClasspath()
里面的内容。
推断项目类型
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
static WebApplicationType deduceFromClasspath() {
//首先判断是不是存在WEBFLUX类型的类,然后在判断是不是存在WEBMVC,在判断是不是存在JERSEY
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
//for循环查找SERVLET相关的包
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
//如果什么都没找到
return WebApplicationType.NONE;
}
}
//如果上述都判断过则判断为SERVLET的Web项目
return WebApplicationType.SERVLET;
}
进入后,可以看到首先判断的就是:这个项目是否是一个Web项目,如何判断?就是根据里面有没有Web项目相关的包。if语句里面的静态字段存的就是完整的类名。那么第一个判断就是:如果项目里存在WEBFLUX
,且WEBMVC
和JERSEY
不存在,就判断为REACTIVE
的项目,把WebApplicationType
的类型置为REACTIVE
。接着经过一个for循环去找SERVLET_INDICATOR_CLASSES
里面的内容,SERVLET_INDICATOR_CLASSES
是一个字符串数组,存放的是所有Servlet Web类型相关的类名。如果发现SERVLET_INDICATOR_CLASSES
一个都没有,则说明这不是一个Web项目,于是判断为NONE
意思是这不是一个Web项目。如果上面的条件都不满足,那么就说明是一个Servlet的Web项目,这个值后面还会用到,作为启动需要验证的条件之一。说完这个跳出来继续下一个方法。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))
这里面是方法里套方法,先看外面的方法setInitializers(…)
,就是进行了一个传递。
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
//这里把拿出来的值全部存到initializers里面,这里后面还会用到
this.initializers = new ArrayList<>(initializers);
}
初始化ApplicationContextInitializer
看到外面的的方法里没有什么东西,那么主要的就是getSpringFactoriesInstances(ApplicationContextInitializer.class)
方法了。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
//type:ApplicationContextInitializer.class注意这个type
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
继续往getSpringFactoriesInstances(type, new Class<?>[] {})
里走,注意传进去的类型是ApplicationContextInitializer.class
。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//参数type:ApplicationContextInitializer.class
ClassLoader classLoader = getClassLoader(); //首先有一个class loader
// Use names and ensure unique to protect against duplicates
//创建一个set用来存放实例化出来的bean的名字,从官方注释看这里明现是想要去重
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//把这些拿出来的类名进行初始化,最终返回一个实例列表
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//排序
AnnotationAwareOrderComparator.sort(instances);
//返回实例列表instances
return instances;
}
可以看到这个方法其实也没有什么复杂的,但是十分重要。一句一句的分析,首先构建一个classloader
,然后用传进来的type
和classloader
一起产生了一个存放名字的set
。那么就进入loadFactoryNames()
看下到底要读取什么东西。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
继续往loadSpringFactories()
里面走。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try { //在这里拿到资源FACTORIES_RESOURCE_LOCATION的内容
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()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
最终我们发现所有需要加载的内容都是从FACTORIES_RESOURCE_LOCATION
变量里来的,变量里面存的是一个文件,这个是什么东西呢?
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files. 这个变量里存得是一个文件的路径
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
就是Spring项目在打包的时候生成的配置文件,每一个Spring的jar包基本都有,就是下面这个位置的文件。要说明的是这里由于循环的是整个项目,也就是说所有叫做spring.factories
的文件都会被拿出来查找,而且官方注释也说了不止一个:Can be present in multiple JAR files.
。位置就在每个jar包的这里。
里面的内容如下,请注意红框部分,第一个是我们本次传进来的type: ApplicationContextInitializer.class
。第二个红框就是我们下面一个大方法要说的类型。
看到这里不难猜出,这部分的内容就是把和type
相关的类全部加载进来,记得刚才是一个存放名字的set
,那么后一句。
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
就是要把他们全部实例化出来,放到一个instances
对象里,返回出去。
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) { //根据names循环创建对象
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
//拿到构造方法
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
//反射创建实例
T instance = (T) BeanUtils.instantiateClass(constructor, args);
//添加到list里面
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}//返回出去
return instances;
}
其实可以看到这个方法就是去找到对应的类的构造方法,反射构造出来,再添加到List
里面返回出去。并且在setInitializers()
方法里面给全局变量this.initializers
赋值,以备他用。
最终setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))
这行代码的作用就是:
进行了所有和ApplicationContextInitializer
相关的初始化。内部使用getSpringFactoriesInstances(...)
方法将项目中所有和ApplicationContextInitializer
有关的类名全部拿出来,并全部实例化。然后经过setInitializers(...)
方法对this.initializer
进行初始化,从搜索spring.factories
和调试的结果来看, SpringBoot会在一开始初始化7个ApplicationContextInitializer
类。这七个对象会在后面有各自的用途。
列表如下:
org.springframework.context.ApplicationContextInitializer=\ /*注意这一行不是要实例化类名,是标识*/
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ 1
org.springframework.boot.context.ContextIdApplicationContextInitializer,\ 2
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ 3
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\ 4
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer 5
----分割,下面的在另一个spring.factories文件里----
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ 6
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener 7
初始化ApplicationListener
那么SpringApplication构造方法里还有另一行初始化的代码。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
其实这个方法已经没有什么好说的了,因为细心的同学一定已经看到了,其实这两句基本上是一样的,只是这里传递的参数是ApplicationListener.class
,也就是说这个方法会把和ApplicationListener
相关的类从spring.factories
文件中取出来实例化好,然后放到另外一个全局变量listeners
里面,以备他用。
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>(listeners);
}
在行这里进行了所有和ApplicationListener
相关的初始化,这里的代码和上面的一样,只不过传递的类不一样而已。这里也就是SpringBoot的观察者模式的Listener
初始化,SpringBoot也就是基于这些观察者编程的。这句话结束了,this.listeners
这个对象里面就有数据了。
列表如下:
org.springframework.boot.ClearCachesApplicationListener,\ 1
org.springframework.boot.builder.ParentContextCloserApplicationListener,\ 2
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ 3
org.springframework.boot.context.FileEncodingApplicationListener,\ 4
org.springframework.boot.context.config.AnsiOutputApplicationListener,\ 5
org.springframework.boot.context.config.ConfigFileApplicationListener,\ 6
org.springframework.boot.context.config.DelegatingApplicationListener,\ 7
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ 8
org.springframework.boot.context.logging.LoggingApplicationListener,\ 9
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener 10
----分割,下面的在另一个文件里----
org.springframework.boot.autoconfigure.BackgroundPreinitializer 11
到此,SpringBoot把ApplicationContextInitializer
和ApplicationListener
相关的所有类都new出来了,以备后面使用。这里比较类似SpringMVC一开始把后置处理器全部从jar包里找到并且new出来的步骤。
推断主类
这个构造方法里最后的一个方法deduceMainApplicationClass()
,是为了识别我们项目里main()
方法的类是什么类型,话句话说就是为了给SpringBoot标识它的启动类在哪个类上。
private Class<?> deduceMainApplicationClass() {
//Spring在这里做的主要工作就是抛异常,拿到异常栈的信息。循环异常信息,发现哪个里面有main()方法,就判断哪个就是启动类(主类)
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
总结
本篇介绍了SpringBoot启动时构造方法里做了一些初始化的工作:首先做的就是推断项目类型,然后将所有ApplicationContextInitializer
和ApplicationListener
全部实例化出来备用,最后把程序入口main()
方法找出来。那么我们下一篇【SpringBoot深度探究(八)源码探究启动流程之二】将会继续追踪:当SpringBoot环境初始化好了以后run()
方法里又做了什么内容。