7. 自动装配源码分析终于来到了大家喜闻乐见的部分:源码分析
在我们前面6节学习了各种”招式“之后,让我们请出对手:springboot
现在在你面前的是一个springboot”空项目“,没有添加任何依赖包和starter包
启动项目:
正常启动,让我们从@springbootapplication开始研究
7.1 @springbootconfiguration会看到@springbootapplication这个注解由好多注解组成
主要的有以下三个:
@springbootconfiguration@enableautoconfiguration@componentscan先来看第一个:@springbootconfiguration
进入这个注解之后会发现
原来你就是一个@configuration啊,一个javaconfig配置类
那我们使用javaconfig不就是用来配置bean吗,所以有了这个注解之后我们可以在springboot运行的主类中使用@bean标签配置类了,如下图所示:
7.2 @componentscan这个注解相信大家都认识了,组件扫描
这个扫描的范围是:springboot主启动类的同级路径及子路径
7.3 @enableautoconfiguration来看这个注解,也是最核心的内容
这个注解怎么这么眼熟啊,还记得刚才的@myenableautoconfig注解吗?就是我们自己写的那个注解
进入@enableautoconfiguration:
看图中红圈位置的注解:@import(autoconfigurationimportselector.class)
是不是跟我们上面自己写的内容一样!
这里的作用便是导入了 autoconfigurationimportselector 这个类的bean定义
我们都知道,如果这个类实现了importselector接口,那他肯定重写了一个方法,就是我们上面重写过的selectimports方法:
果然,在这个类里面确实有这个selectimports方法:
我的天,好长的一串代码,一行都放不下!
此时此刻,我又回想起了在家乡的母亲,夏天的蝉鸣,池塘的荷花…
等等等等,这个类我们当时返回的是什么?是一个字符串数组string[ ],那这个类无论多么长,返回的肯定就是一个字符串数组,不信你自己看:
这个字符串数组存放的内容我们是否清楚呢?当然清楚了!我们返回的是要加载的config配置文件的全包名,通过返回这个全包名,我们就能自动装配上这些配置文件下定义的bean对象,从而达到了自动装配的目的!
根据刚才我们自己实现的selectimports方法,我们是通过注解类的名字来查找,并且最终得到需要加载的config类的全类名,最后返回的。
因此,这里必然有一个根据注解类名字来查找相应的config文件的操作
我们继续反推,看到返回时的定义如下:
我们发现autoconfigurationentry中保存着我们需要的配置信息,它是通过getautoconfigurationentry方法获取的,于是我们继续深入,进入getautoconfigurationentry方法
这一段代码真是把人难住了,好大一片,不知道在做什么
此时此刻,我又回想起了在家乡的母亲,夏天的蝉鸣,池塘的荷花…
回家!有了!我们先想这个方法应该返回什么,根据我们前面的经验,这里应该返回一个类似于entry的保存了我们需要的配置信息的对象
这个方法返回的是新建的autoconfigurationentry对象,根据最后一行的构造函数来看,给他了两个参数:
configurations, exclusionsconfigurations显然使我们需要的配置文件,也是我们最关心的,而exclusions字面意思是排除,也就是不需要的,那我们接下来应该关注configurations到底是怎么来的
根据我们前面的经验,我们是根据注解类名来从一个配置文件中读取出我们需要的config配置类,这里configurations就代表了config配置类,那么我们应该找到一个入口,这个入口跟注解相关,并且返回了configurations这个参数。
正如我们所料,这个方法的参数确实传递过来了一个东西,跟注解有关:
看见那个大大的annotation(注解)了吗!
那么根据这条”线索“,我们按图索骥,找到了三行代码,范围进一步缩小了!
此时再加上返回了configurations,我们最终确定了一行代码:
就是这个getcandidateconfigurations方法,符合我们的要求!
从字面意思上分析,获取候选的配置,确实是我们需要的方法
ok,让我们继续前进,进入这个方法:
这个方法是不是也似曾相识呢?我们之前写过一个专门用于读取配置文件的类mypropertyreader,还记得吗?
如果你还记得的话,我们自己写的工具类里面也是一个静态方法readpropertyforme来帮我读取配置文件
但是我们的配置文件路径一定是需要指定的,不能乱放。
从这个loadfactorynames方法体来看,好像没有给他传递一个具体路径
但是从下面的assert断言中,我们发现了玄机:
在meta-inf/spring.factories文件中没有找到自动配置类config,你要检查balabala。。。。
根据我不太灵光的脑袋的判断,他的这个配置文件就叫spring.factories,存放的路径是meta-inf/spring.factories
于是我们打开spring boot自动装配的依赖jar包:
那这个配置文件里面的内容,是不是跟我们想的一样呢?
原来如此。
这里的enableautoconfiguration注解,正是我们此行的起点啊…
到这里,自动装配到底是什么,应该比较清楚了,原来他是帮我们加载了各种已经写好的config类文件,实现了这些javaconfig配置文件的重复利用和组件化
7.4 loadfactorynames方法行程不能到此结束,学习不能浅尝辄止。
我们还有最后一块(几块)面纱没有解开,现在还不能善罢甘休。
让我们进入loadfactorynames方法:
这个方法非常简短,因为他调用了真正实现的方法:loadspringfactories
这一行return代码我复制在下面:
loadspringfactories(classloader) .getordefault(factorytypename, collections.emptylist());可以分析得出:loadspringfactories方法的返回值又调用了一个getordefault方法,这明显是一个容器类的方法,目的是从容器中拿点东西出来
就此推测:loadspringfactories返回了一个包含我们需要的config全类名(字符串)的集合容器,然后从这个集合容器中拿出来的东西就是我们的configurations
让我们看这个loadspringfactories方法:
它确实返回了一个容器:map 这个容器的类型是:multivaluemap
这个数据结构就非常牛逼了,多值集合映射(我自己的翻译)简单来说,一个key可以对应多个value,根据他的返回值,我们可以看到在这个方法中一个string对应了一个list
那么不难想到multivaluemap中存放的形式:是”注解的类名——多个config配置类“ 让我们打个断点来验证一下:
果然是这样,并且@enableautoconfiguration注解竟然加载了多达124个配置类!
接下来我们继续思考:我们来的目的是获取configurations,所以无论你做什么,必须得读取配置文件,拿到configurations
于是我们在try方法体中果然发现了这个操作:
他获取了一个路径urls,那么这个路径是否就是我们前面验证的meta-inf/spring.factories呢?
我们查看静态常量factories_resource_location的值:
果真如此,bingo!继续往下看,果然他遍历了urls中的内容,从这个路径加载了配置文件:终于看到了我们熟悉的loadproperties方法!
那我们大概就知道了,他确实是通过找到路径,然后根据路径读取了配置文件,然后返回了读取的result
这就是loadfactorynames方法的内部实现。
7.5 cache探秘到这里有的人又要问了:是不是结束了?其实还远没有!
细心地朋友已经发现了玄机,隐藏在loadfactorynames方法的开头和结尾:
喂喂,这个返回的result好像并不是直接new出来的哦
它是从cache缓存中取出来的,你发现了没有
根据下面的if判断,如果从缓存中读取出来了result,并且result的结果不为空,就直接返回,不需要再进行下面的读写操作了,这样减少了磁盘频繁的读写i/o
同理,在我更新完所有的配置文件资源之后,退出时也要更新缓存。
7.6 getautoconfigurationentry再探关键部分已经过去,让我们反过头来重新审视一下遗漏的内容:
还记得getautoconfigurationentry方法吗?
我们最后来研究一下这个类除了getcandidateconfigurations还干了哪些事情:
removeduplicatesconfigurations.removeall(exclusions)可以看到,这里对加载进来的配置进行了去重、排除的操作,这是为了使得用户自定义的排除包生效,同时避免包冲突异常,在springboot的入口函数中我们可以通过注解指定需要排除哪些不用的包:
例如我不使用rabbitmq的配置包,就把它的配置类的class传给exclude
@springbootapplication(exclude = {rabbitautoconfiguration.class})8. 自动装配本质我的理解:
springboot自动装配的本质就是通过spring去读取meta-inf/spring.factories中保存的配置类文件然后加载bean定义的过程。如果是标了@configuration注解,就是批量加载了里面的bean定义如何实现”自动“:通过配置文件获取对应的批量配置类,然后通过配置类批量加载bean定义,只要有写好的配置文件spring.factories就实现了自动。9. 总结spring boot的自动装配特性可以说是spring boot最重要、最核心的一环,正是因为这个特性,使得我们的生产复杂性大大降低,极大地简化了开发流程,可以说是给我们带来了巨大的福音了~~
笔者本人对源码的理解仍然没有那么深刻,只是喜欢分享自己的一些学习经验,希望能和大家共同学习,毕竟掌握一门新技术的快感嘛… 大家都懂的!
写这篇文章耗费了巨大的精力,每一个字均是手码,真的希望喜欢的朋友可以点赞收藏关注支持一波,这就是对我这个未出世的学生的最大激励了!
4开关降压-升压转换器满足USB新供电要求
速显微电子项目入选2023中国汽车供应链优秀创新成果
PCB打样中常见的孔问题全面解析
高精度的WiMAX/3G现场测试解决方案
基于无人机高光谱影像的黑土区玉米农田土壤有机质估算
初学者必看的SpringBoo自动装配原理4
MINI-C函数可以带参数了
物联网存在巨大的不确定性该怎样做好应对准备
电磁场基本概念介绍
7颗 4颗 12颗光束 四合一 控制板 共阳极架构灯具芯片
在桥梁上安装传感器,互联网远程监控桥梁状况
OPPO开发者大会官宣时间
联通发布了全新的互联网套餐每月8元可享受15GB的网易应用专属流量
LFP电池开始走向国际舞台
用于监视系统温度、电压和电流的LTC2990
3dd15d参数详解
ic芯片封装工艺及结构解析
ESP8266硬件
一种能够显著提升客制化FPGA原型板验证效率的创新方法浅析
2018阿里云双12年终大促主会场全攻略