引子
在《Spring源码的学习方法和知识地图》里,我提到如果一开始就研究Spring源码会被Spring源码中的细枝末节带偏,脱离主线。因为其中99%的代码是为了解决1%的问题。所以我写了三篇手撕Spring源码的文章:
《手撕spring核心源码,彻底搞懂spring流程》
《手撕Spring源码(二),彻底理解Spring后置处理器》
《手撕Spring源码(三),彻底理解Spring循环依赖原理》
旨在帮助大家把细枝末节去掉,回归Spring源码的设计本身,看代码的核心部分都是在解决什么问题。
我猜测由于三篇文章之间的依赖关系。前面没看透,后面看不懂。所以每篇文章的阅读量下降一半:
看到这个数据,觉得我是不是会灰心沮丧呢?是的,非常沮丧。好久没见过这么惨的阅读量数据了。写Spring源码我也算是拿出看家本领了,因为很多年前我就对它很熟了。赌气在想还是不写系列文章了。
这时又想到了自己的初心。写文章就是希望大家可以一起成长,为祖国的IT事业发展尽一份力。而相互没有多少关联的文章对技术的提升作用很小。虽然阅读量不高,但总算有200多个朋友坚持下来了。为了他们我也要再努力一下。
上面的关联文章读过的最好,没读过的也能看懂。今天从另外一个角度进行梳理。越是有点难度的事情,开头就要从各个角度重新梳理,不断强化。终能达到治学三境界:
第一境界
昨夜西风凋碧树,独上高楼望尽天涯路
第二境界
衣带渐宽终不悔,为伊消得人憔悴
第三境界
众里寻他千百度,蓦然回首,那人却在灯火阑珊处
准备工作
首先咱们还是来写一个最简单的例子:
用的还是 https://github.com/xiexiaojing/yuna 里的代码,只是标签和引用都换成了Spring原生的。
配置类就是配置了扫描路径:
在可以被扫描的包路径下定义了一个Bean对象。用了Component注解这里就可以理解为Bean对象了。
运行成功,通过Spring的IoC容器成功的获取了UserService的Bean对象,并调用了其test方法。
在《把对象交给spring管理的3种方法及经典应用》中我提到Spring有5个开天辟地的Bean。还列出了怎么通过SpringBoot的debug去找。这里其实在debug日志里就能看到把它们打印出来了:
大家不要忽略启动日志,读懂启动日志能获得很多信息,今天咱们就把这段启动日志Spring相关的部分分析透。
Spring启动日志第一行
org.springframework.context.annotation.AnnotationConfigApplicationContext
-
Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5bfbf16f
上面是Spring日志的第一行,翻译成中文就是刷新注解配置应用上下文。咱们来看源码:
进入这个实例化方法中看Spring的源码:
共三行,咱们来解读一下:
this初始化方法,里面初始化的后面都要用到,用到的时候再看,这一行可以忽略。剩余两行,一行作用是注册Bean的定义,另外一行是完成Bean的定义。
先时候注册Bean的定义。什么叫Bean的定义呢。如果Bean是一个产品,那Bean定义就是它的规格说明书。就是一个文档说明,这时候Bean还没有开始创建。注册Bean定义还有一种通过扫描来实现的方式:
这时候进入Spring注解配置应用上下文的实例化方法变成这个:
第二行register方法变成scan方法,其他没有变化,这说明scan方法和register方法完成了两种的功能。就是BeanDefinition。scan的原理我在《手撕spring核心源码,彻底搞懂spring流程》里详细讲过,这就和之前的内容对上了。
创建的事情要交给Bean工厂,也就是refresh方法要做的事情。
其实Bean工厂能不能完成Bean的创建等生命周期管理呢?可以。咱们的测试例子也可以这么写:
我直接用Bean工厂创建一个Bean也能实现。那ApplicationContext应用上下文与BeanFactory的区别在哪里呢?
BeanFactory可以理解为一个汽车工厂,它就是生产汽车的。 别人需要告诉他汽车怎么生产,看我例子里BeanDeclaration实际有很多参数需要我来添上,我展开给大家看看:
ApplicationContext 可以理解为汽车4s店。他提供一条龙服务。我只需要告诉他,我需要哪个汽车,其他都不需要管了。4s店自己帮我处理好。
咱们来看refresh方法,synchronized是多线程同步的,不需要管。第一个运行的方式是prepareRefresh。
进入这个方法就找到了第一行日志打印的地方:
咱们来看prepareRefresh主要做的4件事情:
1、初始化占位符属性的资源
2、检验所有必需的属性都是可解析的
3、存储应用监听器或者应用监听器半成品
4、初始化应用事件半成品
半成品也叫二级缓存,《手撕Spring源码(三),彻底理解Spring循环依赖原理》里有详细介绍过bean的二级缓存。
这四件事情不是本文的核心内容仅做了解。其实这四件事情对下文没有任何推动作用,这就是源码存在太多细枝末节,容易干扰视线的原因。所以要带着明确的目的去看。
Spring启动日志第二行
org.springframework.beans.factory.support.DefaultListableBeanFactory
-
Creating
shared
instance
of
singleton
bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
这是Spring开天辟地的五个Bean中的第一个。类型是
ConfigurationClassPostProcessor
它是一个BeanFactory的后置处理器,因此它的主要功能是参与BeanFactory的建造,在这个类中,会解析加了@Configuration的配置类,还会解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。
找一行日志打印的位置有点困难,咱们用debug的方式找到哪一行打印的。最终定位在这里:
下面咱们站在上帝视角重新回到refresh方法:
refresh的前面都是在组建Bean工厂,从533行也就是内部会打印咱们这条日志的这一行开始调用Bean工厂后置处理器。Bean工厂后置处理器与Bean后置处理器不是一回事。Bean工厂后置处理器的作用就是前面Bean工厂组建完成,现在可以把组建好的Bean工厂传给这个后置处理器。后置处理器自己可以决定要对Bean工厂做些什么。
因为最重要的方法都在这里面,咱们直接来看
PostProcessorRegistrationDelegate
里面做了什么
日志是从标红的那行代码打印的,这是触发实现了高优先级的
BeanDefinitionRegistryPostProcessor
进行实例化并注册成后置处理的类。从这里咱们可以得出结论:Spring给它排了很高的优先级,对于Spring默认来说配置类解析器是最先被加载的后置处理器。
Spring启动日志第三行
org.springframework.context.annotation.ClassPathBeanDefinitionScanner
-
Identified
candidate
component
class:
file [D:\yuna\target\classes\com\brmayi\yuna\newspring\UserService.class]
这是验证用户bean是否符合要求的,可debug查看不详细解释。
Spring其他4个开天辟地的Bean
其他的由于方法相同,这里只介绍其他4个Bean的作用:
EventListenerMethodProcessor和DefaultEventListenerFactory
DefaultEventListenerFactory是一个Bean工厂,
EventListenerMethodProcessor 是 Bean工厂的一个后置处理器, 用来对 @EventListener 提供支持。
AutowiredAnnotationBeanPostProcessor
使用Spring 编程时,使用Ioc,我们只需要声明对象,而由Spring 替我门自动注入,
而其中起重要作用则为
AutowiredAnnotationBeanPostProcessor,
它在bean实例化后,进行这重要的初始化操作。
CommonAnnotationBeanPostProcessor
这个Bean后置处理器通过继承
InitDestroyAnnotationBeanPostProcessor
对@javax.annotation.PostConstruct
和@javax.annotation.PreDestroy
注解的支持。以及依据bean name依赖注入的
@javax.annotation.Resource支持。
总结
本文介绍了使用查日志所在位置的方式学习源码。这种方式在文中我也特意举例说明了容易被带偏,偏离重心,要做好时间和学习规划,才能达到好的效果。
学习Spring不是一件容易的事,我之前学习的时候放弃过很多次。这期间不断变换学习方法重新学。最终突然达到了“蓦然回首”的顿悟。从此再看Spring都不是事儿。
从那之后再也不会因为手欠升级了某个版本或者删除了某些理论上不相关的依赖,程序运行不起来各种尝试才搞定。而变成了遇到问题,呆呆的想上一小会,定位一段源码验证一下猜想,做个简单的调整就搞定了。
假期哪里也去不了,在家看了一些过去的高分电影。很多电影反映了一些底层贫苦人民暗无天日的生活,一直到最后,他们的生活仍然没有一丝希望。咱们现在可能会经历一些小的痛苦,比如学Spring就很苦,但是这是带着希望的苦。喝下之后就像咖啡一样回味悠远~
编程一生
因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。
PDCA方法论,检查自己是否错过更新:每周三晚上8点左右,我都会更新文章,如果你没有收到,记得点开【编程一生】公众号找一下(*^▽^*)