spring底层分析(加载 - 解析 - 生成beanDefinition对象)
1.在正式解析spring底层前,我先简单讲一下什么是spring(以方便大家的理解):
spring官方的解释:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架;
说得直白一点,spring就是一个"容器"框架,是一个什么容器框架,就是一个帮我们创建管理bean的"容器"框架,当知道spring是一个容器之后,是一个帮我们创建管理bean的容器框架后,我们就来正式的解析spring的底层
2.首先我们先搭建一个spring项目(通过最原始的xml方式)
这是我们最传统的xml的spring项目,首先这里我们需要创建一个xml配置文件,其次将对应的xml配置文件传递给我们spring容器;不知道大家有没有想过,在这里为什么要创建xml配置文件,而且为什么xml配置文件要传递给spring容器?因为这里,前面我说到了,spring其实就是一个容器,那对于目前的spring容器来说,它现在要做什么他不知道(它现在就只有里面的实现规则),所以此时就需要一个配置文件传递给spring框架,告诉spring它目前需要做什么,需要怎么做;所以在这里其实就是告诉spring容器,要怎么去帮我们创建实例化bean(就是按照我们的规则去创建实例化bean)。
好,这里上面我们知道了,然后就真正的去深入spring底层,到底是怎么去"加载(加载配置文件) - 解析(解析配置文件) - 生成bean"
3.0
进入到spring容器后,我们可以看到,这里就三个方法;那么我们现在就来真正了解这三个方法,分别是做什么,有什么用,方法里面又是什么
3.1 super(parent)此时parent为空,这里就一直调用父级构造方法,然后在AbstrectApplicationContext中创建父容器;这里这个方法我就不做过多阐述,有兴趣可下来自行去了解深究一下
3.2 setConfigLocations:将我们传进的配置文件解析成资源路径赋值给AbstractRefreshableConfigApplicationContxt中的configLocations属性,后面会使用
3.3 refresh() 启动IOC容器,是载入BeanDefinition的入口,正在加载-解析-生成beanDefinition的主要逻辑方法(为什么是生产beanDefinition(bean的定义对象)对象,而不是直接生产bean对象,我 后面再解释);
然后进入refresh()方法。
4.refresh()方法
进入到refresh()方法后,我们可以看到有一堆方法,此时可能有的同学就会有点慌了,一堆方法,我怎么知道那个方法是做什么功能的;是的,确实存在这个问题,但是我们只需要暂时明确一点,spring是一个容器,是一个按照我们的配置文件,生成管理bean的容器,那么我们现在暂时就只需要关注那个方法是在做解析配置文件,然后生成beanDefinition对象地方。
4.1 在这里,第一个方法prepareRefresh():为Ioc容器的启动设置了启动时间等配置
obtainFreshBeanFactory():解析配置文件,生成beanDefinition对象的地方,这里我们主要看的就是这个方法
4.2 进入obtainFreshBeanFactory()方法内部
继续,是两个方法,我们在继续往里面走(在后面博主我就不对每个方法做太多解释(语言组织语言表达能力有限))
进入到refreshBeanFactory方法后,这里好像有一些重点代码了(当然spring容器的每一步代码都是重点),这里出现我们IOC的bean工厂了(我相信只要是从事java工作的开发者,都应该或多或少的听说过BeanFactory是spring一个非常核心的接口,它定义了管理bean的最基本方法);希望大家能记住这个工厂DefaultListableBeanFactory:它是正在实现生成保存beanDefinition(bean的定义对象)的工厂;我们接着继续往下走,继续看spring是怎么 加载配置文件-解析-生成bean定义对象的,继续看加载解析生成beanDefinition的方法
4.3 loadBeanDefinitions(beanFactory);
XmlBeanDefinitionReader:XML配置文件的读取是Spring中重要的功能,因为Spring的大部分功能都是以配置作为切入点的
4.4 继续往下走:loadBeanDefinitions(beanDefinitionReader)
4.4.1 getConfigLocations()方法
不知道大家还有没有影响,之前spring将我们传入的配置文件,解析成资源路径,然后保存到了AbstractRefreshabkleConfigApplicationContext的configLocation属性中,这里就在获取之前保存的资源路径,为解析配置文件做准备
4.5 继续往下走
在这里,我们发现spring生成了一个资源加载器,并且通过资源加载器+资源路径,生成了Resource[]对象,ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource,Resource[]对象内部看一下
4.5.1 Resource(对spring内部用到的资源做一些处理)
进入到Resource之后,我们发现其内部定义了很多方法(这里我们先不管,感兴趣的同学可以自信了解一下)。我们还发现Resource继承了InputStreamSource接口
4.5.2 InputStreamSource接口
我们可以看到进入到InputStreamSource后,里面就只有一个方法getInputStream():通过这个方法spring就可以将配置文件读取到spring内部了 (对于我们编写的java开发所需要的一切东西(不一定准确哈,我在这里主要想表达我们的.xml是一个文件),皆是一文件保存下来的,不论是.html也好,还是.xml文件也好,还是.java文件也好,都是文件,只是其对应的文件格式不同,但本质是文件)
4.6 当spring获取到Resource,那么接下来就可以对我们的配置文件做操作了(终于到配置文件了)
进入loadBeanDefinitions方法
在这里,我们可以看到,spring果然调用了getInputStream()方法,获取文件流,继续往下走
4.6.1 doLoadBeanDefinitions方法
在这里就是真正的加载 - 解析 - 验证xml文件是否正确,并生成Document对象了,全是通过
doLoadDocument方法;
在这里具体是怎么解析 - 验证配置文件的,我不做过多诠释(比较复杂,这涉及到xml文件的校验:DTD与XSD),感兴趣的同学,请下来自信了解,了解完之后如果不介意,请分享一下给博主我
4.7 继续往下走,在这里我们已经加载 - 解析并生成了Document对象了,spring帮我们生成BeanDefinition对象已经完成了2步了,目前就只差最后一步了,那么我们接着忘下走
在这里我们终于看到与beanDefinition对象相关的信息了
4.8 继续往下走 documentReader.registerBeanDefinitions方法
4.8.1 doRegisterBeanDefinitions(Element root) 方法
4.9 parseBeanDefinitions(root, this.delegate) 继续深入
进来之后,这里有一个判断,根据判断结果,对不同的标签做处理,这里我们看xml自带的标签解析
4.10 parseDefaultElement
在这里,我们就可以看到spring对xml里的标签做处理了
4.11 继续深入(看解析bean标签)processBeanDefinition
这个方法里面就在解析,生成beanDefinition对象了
4.12 继续深入parseBeanDefinitionElement
好了在这里,我们已经可以看到spring已经在解析xml文件里面对应的标签,并解析其对应的属性,并生成beanDefinition对象了
总结:spring容器启动,加载bean的过程,其实就是:加载xml文件,获取Resource对象,在通过Resource获取到Document对象,在通过Document对象解析生成beanDefinition对象,并保存到beanDefinitionMap中(这是一个Currenthashmap中),然后在通过bean定义对象,生成bean
上面我们说到了beanDefintion对象,而不是直接生成bean,是因为:不知道大家知不知道到,我们通过spring的getBean(beanName)方法,是可以直接获取到对应的对象的,在这个过程中因为只传入了一个bean的名称,那对与spring来说,想要知道我们获取的到底是那个对象,到底是单例还是多例,那肯定要根据这个唯一的beanName来进行解析,因为我们在spring启动的时候,就已经对bean进行解析了(因为要知道bean是单例的还是多例的,是懒加载还是迫切加载,是迫切加载,并且是单例bean,那么在容器启动的时候,就要生成的对应的单例bean并保存到单例池中(也是一个CurrentHashMap)),在这个过程就已经对bean进行了解析,如果在获取的时候,还要对bean进行解析,那么就重复了,而且也耗性能了,所以spring创建了一个beanDefinition对象,专门用来保存bean的定义(以beanName作为key,以bean定义对象作为值保存到beanDefinitionMap中),在创建/获取bean的时候,直接通过bean定义对象,就可以获取到bean的所有信息了,然后就可以创建/获取Bean了
CSDN-Ada助手: 恭喜你写了第6篇博客!标题听起来很有深度,我对了解@Transactional注解的底层原理非常感兴趣。你的博客内容一定非常有见地。希望你能继续保持创作的热情,为读者们带来更多有价值的知识。下一步,或许你可以考虑探索一些与@Transactional注解相关的实际应用案例,这样读者们可以更好地理解如何在实际项目中使用它。谢谢你的分享,期待你的下一篇博客!
CSDN-Ada助手: 不知道 Java 技能树是否可以帮到你:https://edu.csdn.net/skill/java?utm_source=AI_act_java
CSDN-Ada助手: 恭喜你这篇博客进入【CSDN每天最佳新人】榜单,全部的排名请看 https://bbs.csdn.net/topics/615603261。