最近写的几篇spring系列的文章,收到了很多读者的好评,有些读者希望我再多写几篇这方面的文章。甚至还有读者私信给我,向我请教看spring源码的方法,为此我打算写一个spring源码解读的系列,回馈给一直支持我的粉丝们。
不知道你有没有这些经历:
想看spring的源码无从下手
spring源码太多,看着看着就跟丢了
不知道哪些是主要的,哪些是次要的
前几天还记得,今天就忘了
spring
源码很复杂,说实话这类文章不好写,想把它讲清楚很难,写着写着篇幅会很长,读者不一定有耐心看下去,而且看完容易忘记。
我打算用图文相结合的方式,去除糟粕,只解读一些精华部分,给读者们在阅读源码时一个清晰的思路,不至于迷路。另外最关键的是,看完之后可以记住很多关键流程。
在spring
的庞大体系中,IOC
(控制反转)贯穿始终,其作用不言而喻。我们就先从IOC
开始,介绍它的主干流程,给有需要的朋友一些指引。
spring容器的顶层接口是:BeanFactory
,但我们使用更多的是它的子接口:ApplicationContext
。
通常情况下,如果我们想要手动初始化通过xml文件
配置的spring容器时,代码是这样的:
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");User user = (User)applicationContext.getBean("name");
如果想要手动初始化通过配置类
配置的spring容器时,代码是这样的:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);User user = (User)applicationContext.getBean("name");
这两个类应该是最常见的入口了,它们却殊途同归,最终都会调用refresh
方法,该方法才是spring容器初始化的真正入口。
顺便提一下,其实调用refresh
方法的类并非只有这两个,我们用一张图整体认识一下:
虽说调用refresh
方法的类有这么多,但我决定用ClassPathXmlApplicationContext
类作为列子给大家讲解,因为它足够经典,而且难度相对来说要小一些。
再次重申一下,由于spring源码代码量巨大,即使我能一次性讲完,恐怕你也没那么多耐心看下去。所以我会采用你好,我也好的方式,忽略一些细枝末节,只抓重点。如果有对某些细节比较感兴趣的同学,欢迎加我微信和我一起交流,或者关注我后续的文章,将会做详细的讲解。
refresh
方法是spring ioc
的真正入口,它负责初始化spring容器。
既然这个方法的作用是初始化spring容器,那方法名为啥不叫init
?
答案很简单,因为它不只被调用一次。
在springboot
的SpringAppication
类中的run
方法会调用refreshContext
方法,该方法会调用一次refresh
方法。
在springcloud
的BootstrapApplicationListener
类中的onApplicationEvent
方法会调用SpringAppication
类中的run
方法。也会调用一次refresh
方法。
这是springboot项目中如果引入了springcloud,则refresh方法会被调用两次的原因。
在springmvc
的FrameworkServlet
类中的initWebApplicationContext
方法会调用configureAndRefreshWebApplicationContext
方法,该方法会调用一次refresh
方法,不过会提前判断容器是否激活。
所以这里的refresh
表示重新构建的意思。
好了,废话不多说。下面重点看看refresh
的关键步骤:
其实上图中一眼看过去好像有很多方法,但是真正的核心的方法不多,我主要讲其中最重要的:
obtainFreshBeanFactory
invokeBeanFactoryPostProcessors
registerBeanPostProcessors
finishBeanFactoryInitialization
obtainFreshBeanFactory
方法会解析xml的bean配置,生成BeanDefinition
对象,并且注册到spring容器中(说白了就是很多map集合中)。
经过几层调用(细节不说,很简单),会调到AbstractBeanDefinitionReader
类的loadBeanDefinitions
方法:
该方法会循环locations
(applicationContext.xml文件路径),调用另外一个loadBeanDefinitions
方法,一个文件一个文件解析。
经过一些列的骚操作,会将location转换成inputSource和resource,然后再转换成Document对象,方面解析。
在解析xml文件时,需要判断是默认标签,还是自定义标签,处理逻辑不一样:
spring的默认标签只有4种:
对应的处理方法是:
注意常见的:
、
、
等都是自定义标签。
从上图中处理
标签的processBeanDefinition
方法开始,经过一系列调用,最终会调到DefaultBeanDefinitionDocumentReader
类的processBeanDefinition
方法。
这个方法包含了关键步骤:解析元素生成BeanDefinition 和 注册BeanDefinition。
自定义属性的内容有趣,但是不这里不会讲,现在用得不多,有兴趣的同学加我微信和我私聊。
下面重点看看BeanDefinition是如何生成的。
上面的方法会调用BeanDefinitionParserDelegate
类的parseBeanDefinitionElement
方法:
一个
标签会对应一个BeanDefinition
对象。
该方法又会调用同名的重载方法:processBeanDefinition
,真正创建BeanDefinition
对象,并且解析一系列参数填充到对象中:
其实真正创建BeanDefinition的逻辑是非常简单的,直接new了一个对象:
真正复杂的地方是在前面的各种属性的解析和赋值上。
上面通过解析xml文件生成了很多BeanDefinition
对象,下面就需要把BeanDefinition
对象注册到spring容器中,这样spring容器才能初始化bean。
在BeanDefinitionReaderUtils
类的registerBeanDefinition
方法很简单,只有两个流程:
先看看DefaultListableBeanFactory
类的registerBeanDefinition
方法是如何注册beanName
的:
接下来看看SimpleAliasRegistry
类的registerAlias
方法是如何注册alias
别名的:
这样就能通过多个不同的alias
找到同一个name
,再通过name
就能找到BeanDefinition
。
上面BeanDefinition
对象已经注册到spring容器当中了,接下来,如果想要修改已经注册的BeanDefinition
对象该怎么办呢?
refresh
方法中通过invokeBeanFactoryPostProcessors
方法修改BeanDefinition
对象。
经过一系列的调用,最终会到PostProcessorRegistrationDelegate
类的invokeBeanFactoryPostProcessors
方法:
流程看起来很长,其实逻辑比较简单,主要是在处理BeanDefinitionRegistryPostProcessor
和BeanFactoryPostProcessor
。
而BeanDefinitionRegistryPostProcessor
本身是一种特殊的BeanFactoryPostProcessor
,它也会执行BeanFactoryPostProcessor
的逻辑,只是加了一个额外的方法。
ConfigurationClassPostProcessor
可能是最重要的BeanDefinitionRegistryPostProcessor
,它负责处理@Configuration
注解。
处理完前面的逻辑,refresh
方法接着会调用registerBeanPostProcessors
注册BeanPostProcessor
,它的功能非常强大,后面的文章会详细讲解。
经过一系列的调用,最终会到PostProcessorRegistrationDelegate
类的registerBeanPostProcessors
方法:
注意,这一步只是注册BeanPostProcessor
,真正的使用在后面。
前面主要介绍了:
spring容器初始化的入口
refresh方法的主要流程
解析xml配置文件
生成BeanDefinition
注册BeanDefinition
修改BeanDefinition
注册BeanPostProcessor
以上内容只是spring容器初始化的前期准备工作,预告一下,真正的好戏在后面的:实例化Bean
、依赖注入
、初始化Bean
、BeanPostProcessor调用
等。
各位亲爱的小伙伴,在公众号中扩展右上角“设为星标”能第一时间看到我的好文章喔,纯干货分享,错过真的可惜。
如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、时间管理有超赞的粉丝福利,另外回复:加群,可以跟很多BAT大厂的前辈交流和学习。
个人公众号
个人微信