springboot源码解析(一)
SpringBoot应用基础结构
我们每创建一个springboot应用就会发现,其目录结构中都会有一个以应用名为首的Application类(下文中都直接称为Application类),而其他包都是在这个类的同级或子级下面,结构如图:
Application类作为应用的启动类,位于项目源码的根目录中,至于为什么结构会这么安排,我们下面会说。
Application类的结构
如上图所示,我们可以看到,最关键的地方有两个:
- @SpringBootApplication注解
- SpringApplication.run()方法
任何的springboot应用都会由这两个部分组成。接下来我就来就这两个地方分析源码。
@SpringBootApplication注解
打开注解的源码我们可以看到,主要由以下几个注解组成:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
@SpringBootConfiguration
@SpringBootConfiguration注解是由@Configuration来注解的,因此也就表示Application类本身就是一个bean。虽然@SpringBootConfiguration注释中说该注解一个应用中只能用一次,但是配置多个也不会报错,只是建议在一个应用中只用一次,并且该注解也可以与@Configuration互换。
@ComponentScan
@ComponentScan注解的功能与我们之前在xml文件配置的的功能是一样的,而上面也提到Application类放在源码的根目录下,其实就是与这个注解有关。@ComponentScan在没有指明basePackages忏属性的时候,默认会扫描该注解所在的类的包及其子包下的所有@Component注解过的类,包括@Controller,@Service,@Configuration这些注解。这也就是为什么我们不用做任何配置,就可以将springboot应用中的类扫描为bean。
@EnableAutoConfiguration
@EnableAutoConfiguration注解,从名字我们也可以看出,是开启自配置配置的。从源码中我们可以看到,该注解中有一个@Import(AutoConfigurationImportSelector.class),而其中发挥自动配置作用就是AutoConfigurationImportSelector类。
从AutoConfigurationImportSelector的源码中可以知道,该类是通过SpringFactoriesLoader来加载自动配置的类的,从而进一步通过这些自动配置的类来完成默认配置。从而这也就解决了,为什么我们使用springboot的时候压根就不需要配置太多,原因就是因为SpringBoot通过自动配置将已经封装在jar包中的自动配置类加载进来生成了bean。而对于SpringFactoriesLoader的原理我们下面会说。
SpringApplication类
Application类是通过SpringApplication类的静态run方法来启动应用的。打开这个静态方法,该表态方法真正执行的是两部分:
- new SpringApplication()
- 执行对象run()方法
在SpringApplication的构造方法中,我们可以看到有如下几个步骤:
- 推断当前的环境是否是web环境,其中springboot2.0中又添加了reactive环境。
- 初始化ApplicationContextInitilizer,其中的原理也是通过SpringFactoriesLoader来实现的。
- 初始化ApplicationListener,原理同上。
- 推断当前启动的main方法所在的类。
推断当前应用环境
springboot在启动时需要推断当前的应用环境,springboot2.0当中一共定义了三种环境:none, servlet, reactive。none表示当前的应用即不是一个web应用也不是一个reactive应用,是一个纯后台的应用。servlet表示当前应用是一个标准的web应用。reactive是spring5当中的新特性,表示是一个响应式的web应用。而判断的依据就是根据Classloader中加载的类。如果是servlet,则表示是web,如果是DispatcherHandler,则表示是一个reactive应用,如果两者都不存在,则表示是一个非web环境的应用。
初始化ApplicationContextInitializer
在介绍初始化之前,先介绍一下SpringFactoiesLoader的原理。
SpringFactoiesLoader会扫描所有jar包中的META-INF/spring.factoies文件,该文件的格式都是key=value的格式。key是一个接口的名字,value是实现类的名字,如果value有多个值,则用逗号分隔。而我们上面提到的@EnableAutoConfiguration也是利用这个原理去扫描所有的自动配置类,以该注解的全限定类名作为key,所有的默认配置类为值。SpringFactoiesLoader内部有一个静态的双层ConcurrentMap,用来存储这些key-value,第一层map的key是classloader,springboot默认的classloader是appClassLoader,value则是一个MultiValueMap,是spring自己实现的一个hashmap。而这个MultiValueMap的key就是spring.factoies文件中的key,value是一个list,即spring.factoies文件中的value。SpringFactoiesLoader利用缓存机制,只在第一次扫描所有的META-INF/spring.factoies文件时,就把所有的key-value都加载到这个map中,后面再进从中获取值时,直接从map中取就可以了,就不需要再重新扫描了。
把ApplicationContextInitializer都加载了之后,还要进行一项工作就是会对所有的ApplicationContextInitializer实现类生成对象,SpringApplication中有一个属性,List类型的initializers,用来存储这些实例化后的对象,这些对象存储之后,会在后面的启动过程中初始化ApplicationContext。
初始化ApplicationListener
ApplicationListener的过程与ApplicationContextInitializer是一样的,不过因为SpringFactoiesLoader已经扫描过一次了,所以这次执行的时候,就会直接从SpringFactoiesLoader中的静态map中取出值即可。同样的,也需要将所有的实现类生成对象,并保存在SpringApplication对象的listener list属性中。
注:springboot初始化过程中主要是扫描两个spring.factoies文件,其中定义的key-value中的类,是整个启动过程中要用到的。一个是spring-boot-2.0.3.RELEASE中的,一个是spring-boot-autoconfigure包中的。这两个jar包中的文件定义整个启动流程中要执行的所有默认配置好的类。
推断整个应用的main方法所在的类
其实我们已经从main方法中启动了,为什么后面还要再推断一下呢?其实这样做的目的,主要是为了将该类的对象存储在SpringApplication的对象中,创建日志Logger和打印日志用的。我们在启动时会看到主类的类名以及其他的打印信息,都是通过该对象来创建logger和打印日志的。
总结:从上面几个步骤中我们可以看出,前期的new SpringApplication()方法中主要是起到了一个预加载的功能,将前期的环境判断,后面要用到的对象都准备好,到run执行的时候就直接拿出来用就好了,也是大大方便了后面run方法执行的过程。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
前端编年史
前言 笔者学习一门知识有一个习惯,就是会先去了解这门知识的一个大致历史框架,而后再学习具体的知识内容。这样做有以下两个目的: 1.可以增添兴趣,对于越枯燥的知识越是有效。 2.可以知道这门知识的来龙去脉,可以知道依托于这门知识,自己将会去向何方。毕竟古人也说过,以史为镜可以知兴替嘛。 然而,查阅无数资料之后,笔者绝望地发现没有一篇完整地准确地描述前端历史的文章可以借鉴,因此萌生了自己动手写一篇一直持续维护的前端编年史以便查阅。 正文 日期 事件 1989 html正式诞生,由物理学家蒂姆·伯纳斯·李为了方便学术文档的分享而创造,这也是前端的起始时间。 1994.10.13 Mosaic Netscape 0.9正式发布,一代浏览器霸主开始展露它的锋芒。 1994.10 Web技术领域最具权威和影响力的国际中立性技术标准机构正式成立,创立者就是html的创造者大神蒂姆·伯纳斯·李。 1995.05 前端中最重要的成员JavaScript(原名LiveScript)诞生,创造者是网景的布兰登·艾奇。他仅仅用了10天的时间就完成了这项创举。 1995.12.24 html2.0标准由...
- 下一篇
为什么尽量别用setInterval
在开发一个在线聊天工具时,经常会有过多少毫秒就重复执行一次某操作的需求。“没问题”,大家都说,“用setInterval好了。”我觉得这个点子很糟糕。 原因之一:setInterval无视代码错误 setInterval有个讨厌的习惯,即对自己调用的代码是否报错这件事漠不关心。换句话说,如果setInterval执行的代码由于某种原因出了错,它还会持续不断(不管不顾)地调用该代码。看演示 原因之二:setInterval无视网络延迟 假设你每隔一段时间就通过Ajax轮询一次服务器,看看有没有新数据(注意:如果你真的这么做了,那恐怕你做错了;建议使用“补偿性轮询”(backoff polling))。而由于某些原因(服务器过载、临时断网、流量剧增、用户带宽受限,等等),你的请求要花的时间远比你想象的要长。但setInterval不在乎。它仍然会按定时持续不断地触发请求,最终你的客户端网络队列会塞满Ajax调用。看示例 原因之三:setInterval不保证执行 与setTimeout不同,你并不能保证到了时间间隔,代码就准能执行。如果你调用的函数需要花很长时间才能完成,那某些调用会被直接...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果