本文已参与「新人创作礼」活动,一起开启掘金创作之路。
入口 main 方法
运行启动类的 main 方法:
@SpringBootApplication
public class CmsApplication{
public static void main(String[] args){
SpringApplication.run(CmsApplication.class, args);
}
}
可以看到,启动类 main 方法中,调用了 org.springframework.boot.SpringApplication 的静态 run 方法。
进入 run 方法,可以看到,最终调用的是 SpringApplication 的构造方法,传入了 CmsApplication.class 参数,创建了 SpringApplication 实例,然后再调用其 run 方法。
也就是说 SpringBoot 启动类也可以写成这样的:
@SpringBootApplication
public class CmsApplication{
public static void main(String[] args){
new SpringApplication(CmsApplication.class).run(args);
}
}
创建 SpringApplication 实例
进入构造方法:
最终调用的是两个参数的构造方法,该方法内源码如下:
1.判断是否要启动 web 容器
WebApplicationType.deduceFromClasspath()方法用来判断该 SpringBoot 是什么类型的应用,deduce 推断。应用的类型有 3 种,在 WebApplicationType 中进行了定义,分别是:
NONE非 web 项目,不需要启动 web 容器。SERVLET传统 Servlet 的 web 程序,需要启动对应的 web 容器支持,如 tomcat。REACTIVE非阻塞的 web 框架,对应 spring-webflux,需要启动支持 reactive 的 web 容器。
判断的逻辑比较简单,就是通过ClassUtils.isPresent()判断 classpath 下是否存在相应的类,来判断要启动的 web 容器类型。
2.加载所有的初始化器 Initializer
这行代码则是获取了所有key = ApplicationContextInitializer的类,并对其实例化。
首先getSpringFactoriesInstances(ApplicationContextInitializer.class)方法源码如下:
2.1 获取类加载器
内部调用的ClassUtils.getDefaultClassLoader(),通过Thread.currentThread().getContextClassLoader()方法,获取到类加载器。
2.2 获取指定 type 类型的所有类名
进入SpringFactoriesLoader.loadFactoryNames()方法,源码如下:
factoryClassName就是传入的 org.springframework.context.ApplicationContextInitializer
进入loadSpringFactories()方法:
① 从缓存 cache 中获取,存在则直接返回。因为这个方法后面用到的次数特别多,所以这里添加了缓存。不过启动流程这里是整个 SpringBoot 中该方法的首次调用,所以 cache 为空,会走后面的逻辑。
可以看到 cache 是一个 ConcurrentReferenceHashMap 类,key 是 ClassLoader,value 是一个Map<String,List>,value 的 key 是 factoryTypeName,value 的 value 是factoryImplementationNames。
② 之前已经获取到类加载器了,所以这里 classLoader 是有值的,这里会调用classLoader.getResources(FACTORIES_RESOURCE_LOCATION)方法
该部分代码会获取所有 classpath 下的 JAR 包中的META-INF/spring.factories文件的路径。
③ 使用 while 循环对每一个文件路径做处理。
UrlResource类是 Spring 提供的,用来访问文件的类,UrlResource resource = new UrlResource(url)就是读取到指定的 spring.factories 文件。
Properties properties = PropertiesLoaderUtils.loadProperties(resource)则是把文件的内容转为了 Properties 对象。 spring.factories 文件是 key=value 类型的。
后面的 for 循环则是将配置内容解析到 map 中,其中 value 是使用 , 拼接,所以再次使用了 for 循环。
以其中一个读取的文件为例:
TIPS:Spring 提供的 StringUtils 的commaDelimitedListToStringArray()方法,可以将字符串以 , 分割为字符串数组。
再看一下 spring.factories 初始化器,以 spring-boot-autoconfigure 下的为例:
2.3 实例化所有 type 类型的类
这部比较简单,就是for 循环中利用反射,分别调用了其构造方法,完成实例的创建。
2.4 对实例化的类进行排序,应该是和这些类的执行的先后顺序有关
进入该方法:
list.sort() 方法中传入了排序规则INSTANCE,而 INSTANCE 是 AnnotationAwareOrderComparator 类的实例,AnnotationAwareOrderComparator 类继承自 OrderComparator 类,而 OrderComparator 实现了 Comparator 接口。
我们知道比较的核心方法是compare() 方法,而 AnnotationAwareOrderComparator 并没有重写该方法,我们直接去父类 OrderComparator 中查看,源码如下:
① 可以看到,排序时首先是根据是否实现了PriorityOrdered来判断的。( 从小到大 )
实现了 PriorityOrdered 接口的类,排在前面。
② 如果两个都实现了,或者都没有实现,则进入 getOrder() 方法
该方法内部则分别进行判断是否是 Ordered 接口的实现类,是否有 @Order 注解,是否有 @Priority 注解,如果这些都没有,则返回Ordered.LOWEST_PRECEDENCE。
这里只会获取优先级最高的 order 值。比如既实现了 Ordered 接口又有 @Order 注解,只会 Ordered 接口的 order 值。
2.5 排完序后,初始化器都都创建好了,保存到 list 中去。
3.加载所有的监听器 Listener
监听器和初始化器的逻辑都是一样的。略过。
是获取key = ApplicationListener的类,并实例化。
4.推断主应用类
从deduceMainApplicationClass()方法名上直接翻译就是推断主应用类,应该是找 main 方法所在的类。进入deduceMainApplicationClass()方法:
可以看到,方法内部是 new 了一个运行时异常 RuntimeException,然后获取其异常堆栈信息 stackTrace,遍历堆栈,找到有 main 方法的类,把该类作为启动类。后续扫描包的范围就确定了下来。
5.至此,创建 SpringApplication 实例的流程就完成了。
- 推断应用类型
- 加载并实例化所有的初始化器
- 加载并实例化所有的监听器
- 推断程序运行主类