当前位置: 首页 > 科技观察

浅显易懂spring的IOC主流程(上)

时间:2023-03-20 18:06:33 科技观察

本文转载自微信公众号《苏三说技术》。作者因为热爱所以坚持ing。转载本文请联系苏三硕科技公众号。前言中最近写的春季系列的几篇文章得到了很多读者的好评,也有读者希望我多写这方面的文章。甚至有读者私信我,问我如何阅读spring源码。为此,我打算写一个spring源码解读系列来回馈一直以来支持我的fans。不知道大家有没有这样的经历:想看spring的源码,却无从下手。spring源码太多了,看的时候就丢了。不知道哪些是主要的,哪些是次要的。前几天还记得,今天忘记了。spring的源码非常复杂。说实话,这种文章不好写。很难解释清楚。写的篇幅会很长。读者可能没有耐心看完,看完后很容易忘记。我打算采用图文结合的方式,去掉糟粕,只解读部分精华,让读者在阅读源码时思路清晰,不至于迷失方向。另外,最重要的是,看完之后可以记住很多关键的流程。在spring庞大的系统中,IOC(inversionofcontrol)贯穿始终,其作用不言而喻。下面先从IOC入手,介绍一下它的主要流程,给有需要的朋友一些指导。入门spring容器的顶层接口是:BeanFactory,但是我们更多的使用它的子接口:ApplicationContext。通常情况下,如果我们想手动初始化通过xml文件配置的spring容器,代码是这样的:你想手动初始化通过配置类配置的spring容器,代码是这样的:AnnotationConfigApplicationContextapplicationContext=newAnnotationConfigApplicationContext(Config.class);Useruser=(User)applicationContext.getBean("name");这两个类应该是最常见的入门了,但是却殊途同归。最后会调用refresh方法,这才是spring容器初始化的真正入口。顺便说一下,其实调用refresh方法的不止这两个类。我们整体来看一下:虽然调用refresh方法的类非常多,但是我还是决定以ClassPathXmlApplicationContext类为例给大家讲解一下,因为够经典,难度也相对较小。重申一下,由于spring源码庞大,就算我一次能看完,恐怕你也没有那么多耐心看完。所以我会采用你好我的方式,忽略一些细节,只关注重点。如果有同学对某些细节比较感兴趣,欢迎加我微信与我交流,或者关注我的后续文章,我会详细讲解。refresh方法refresh方法是springioc的真正入口,负责初始化spring容器。既然这个方法的作用是初始化spring容器,为什么这个方法叫init呢?答案很简单,因为它不会只被调用一次。springboot的SpringAppication类中的run方法会调用refreshContext方法,会调用一次refresh方法。springcloud的BootstrapApplicationListener类中的onApplicationEvent方法会调用SpringApplication类中的run方法。刷新方法也会被调用一次。这也是为什么在springboot项目中引入springcloud会调用两次refresh方法的原因。springmvc的FrameworkServlet类中的initWebApplicationContext方法会调用configureAndRefreshWebApplicationContext方法,会调用一次refresh方法,但是会提前判断容器是否激活。所以这里refresh就是重建的意思。好了,别再瞎扯了。下面重点说一下刷新的关键步骤:其实上图中的方法一眼看上去好像很多,但真正核心的方法并不多。主要讲最重要的几个:obtainFreshBeanFactoryinvokeBeanFactoryPostProcessorsregisterBeanPostProcessorsfinishBeanFactoryInitialization解析xml配置文件obtainFreshBeanFactory方法会解析xml的bean配置,生成一个BeanDefinition对象注册到spring容器中(说白了就是在很多地图集)。经过几层调用(不说细节,很简单),会转到AbstractBeanDefinitionReader类的loadBeanDefinitions方法:这个方法会循环locations(applicationContext.xml文件路径),调用另一个loadBeanDefinitions方法,并逐个分析文件。经过一系列的操作,location会转化为inputSource和resource,再转化为Document对象进行解析。解析xml文件时,需要判断是默认标签还是自定义标签。处理逻辑不同:spring默认的标签只有4个:对应的处理方式是:注意常见的:,等都是自定义标签。从上图中处理标签的processBeanDefinition方法开始,经过一系列的调用,最终会转到DefaultBeanDefinitionDocumentReader类的processBeanDefinition方法中。该方法包括关键步骤:解析元素生成BeanDefinition和注册BeanDefinition。自定义属性的内容很有意思,这里就不说了,现在也用的不多。有兴趣的同学可以加我微信私聊。生成BeanDefinition让我们关注一下BeanDefinition是如何生成的。上面的方法会调用BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法:一个标签会对应一个BeanDefinition对象。这个方法会调用重载的同名方法:processBeanDefinition,实际创建一个BeanDefinition对象,解析一系列参数填充到对象中:其实实际创建BeanDefinition的逻辑很简单,直接new一个object:真正复杂的是在前面各种属性的解析和赋值。注册BeanDefinition很多BeanDefinition对象都是通过解析xml文件生成的。接下来需要在spring容器中注册BeanDefinition对象,以便spring容器初始化bean。BeanDefinitionReaderUtils类中的registerBeanDefinition方法很简单,只有两个过程:首先,我们看DefaultListableBeanFactory类的registerBeanDefinition方法如何注册beanName:其次,我们看SimpleAliasRegistry类的registerAlias方法如何注册alias别名:这样多个不同的别名就可以找到同一个名字,然后通过名字就可以找到BeanDefinition了。修改BeanDefinition上面的BeanDefinition对象已经注册到spring容器中了。接下来,如果要修改注册的BeanDefinition对象怎么办呢?在refresh方法中,BeanDefinition对象被invokeBeanFactoryPostProcessors方法修改。经过一系列的调用,最终会到达PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors方法:流程看起来很长,其实逻辑比较简单,主要是处理BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor。BeanDefinitionRegistryPostProcessor本身是一个特殊的BeanFactoryPostProcessor,同样执行BeanFactoryPostProcessor的逻辑,只是多了一个方法。ConfigurationClassPostProcessor可能是最重要的BeanDefinitionRegistryPostProcessor,它负责处理@Configuration注解。注册BeanPostProcessor处理完前面的逻辑后,refresh方法会调用registerBeanPostProcessors注册BeanPostProcessor,功能很强大,后面的文章会详细讲解。经过一系列的调用,最终会到达PostProcessorRegistrationDelegate类的registerBeanPostProcessors方法:注意,这一步只是注册BeanPostProcessor,真正的使用在后面。总结一下前面的主要介绍:spring容器初始化入口refresh方法的主要流程解析xml配置文件生成BeanDefinition注册BeanDefinition修改BeanDefinition注册BeanPostProcessor以上内容只是spring容器初始化的前期准备工作。来看看,真正的好戏在后面:实例化Bean、依赖注入、初始化Bean、调用BeanPostProcessor等。