学习Spring源码分析
学习Spring源码分析
经过周末两天对源码的学习只能说千万别扣细节,那简直就是一个无底洞,现在已经被劝退了。这个文章就是一些资料和零散的学习笔记,属实没时间精力去完善整理这两天学习的东西了。
一些知识流程图
Main函数
我们直接在整个项目的第一步main函数的第一句开始打断点进去逐步调试看源码的整个运行流程
一些重要类的介绍
这里并不齐全,只是在学习的过程中几乎全凭心情捡了几个比较重要的类出来查找了一些资料, 在这里当做一个参考吧。
DefaultListableBeanFactory
父类的作用:
- doGetBean 父子容器查找对象方法
- ListableBeanFactory 把全部bean对象枚举出来
- ConfigurableBeanFactory 用于获取和设置Bean的属性值
DefaultSingletonBeanRegistry
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
可以看到这个单例的Bean注册类使用了三级缓存模式
三级缓存的是匿名内部类或者匿名表达式
详细介绍
原文中的英文注释分别阐述了以下内容(一个段落用一个小标): ⑴、类DefaultSingletonBeanRegistry实现了接口SingletonBeanRegistry,是通用注册表的基础实现类: ①、它允许注册bean对象实例(这些对象实例都是单例模式)。 ②、已经注册的bean对象实例是注册表的所有调用者共享的(所以特别需要注意线程安全)。 ③、可以通过bean名称获得目标bean对象实例。 ⑵、类DefaultSingletonBeanRegistry还支持类DisposableBean对象实例的注册(在关闭注册表时销毁),该实例可能与已注册的bean对象实例相对应。bean对象实例之间的依赖关系也可以注册,以此控制销毁bean对象实例的顺序。 ⑶、类DefaultSingletonBeanRegistry主要作为实现接口BeanFactory的基类,它分解了单例bean对象实例的常见管理(比如注册,获取等等,但不包含创建)。 ①、另一个接口ConfigurableBeanFactory扩展了接口SingletonBeanRegistry。 ⑷、类DefaultSingletonBeanRegistry与类AbstractBeanFactory和类DefaultListableBeanFactory(继承自类DefaultSingletonBeanRegistry)相比,既不涉及bean对象实例概念的定义也不涉及bean对象实例的创建过程,还可以用作委托的嵌套助手(涉及Java设计模式)。 介绍参考文章
执行顺序流程
接着一堆父类的初始化操作, 在这个操作操作过程中就进行了一些属性的初始化赋值操作, 所以后面的时候使用一些对象不用奇怪。
执行了super父类的初始化之后设置配置文件属性,最后执行refresh操作
refresh函数
这两天可以说就是学习了这一个函数,整个函数很关键,项目所需de全部Bean初始化操作都在这里完成了,也可以说这两天就只是学习了这个函数。
refresh的天书时序图:
解析XML文件
org.springframework.context.support.AbstractApplicationContext#refresh
org.springframework.context.support.AbstractApplicationContext#obtainFreshBeanFactory
org.springframework.context.support.AbstractApplicationContext#refreshBeanFactory
org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory
org.springframework.context.support.AbstractRefreshableApplicationContext#loadBeanDefinitions
org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
在这个beanDefinitionReader对xml中的Bean对象进行解析.
之间的三个set函数设置beanDefinitionReader对象属性:
1.设置系统的一些环境变量Enviroment
2.设置资源加载器
3.设置实体处理器,用于将xml文件的Bean标签实例化,但是这里不会进行处理,只是指定一个处理器而已
实体处理器
进入处理器的构造函数:
org.springframework.beans.factory.xml.ResourceEntityResolver#ResourceEntityResolver
org.springframework.beans.factory.xml.PluggableSchemaResolver#PluggableSchemaResolver(java.lang.ClassLoader)
这里这个文件就是本地xml文件规范的资源文件,在xml文件中定义有一个规范文件的网络地址,但是如果断网离线的时候我们加载不了那个规范文件,这时就会加载本地的这个文件作为解析规范对xml文件进行解析。
设置完类处理器后对这个beanDefinitionReader传入initBeanDefinitionReader函数中设置是否对配置文件进行验证,默认为true
解析xml文件
读取配置文件
最后将beanDefinitionReader传入loadBeanDefinitions函数进行xml文件的解析
这里的getConfigLocations对应的变量在执行refresh之前就已经执行set函数设置好了:
在AbstractBeanDefinitionReader#loadBeanDefinitions执行循环处理configLocations中的每一个xml资源文件
跟进里面的this.loadBeanDefinitions(location)
函数
再次调用了类内函数,跟进this.loadBeanDefinitions(location, (Set)null)
在getResources(location)
中获得符合我们设置的xml文件名ANT表达式的资源文件地址, 放到resources数组中
this.loadBeanDefinitions(resources)
遍历资源路径数组
在这里使用循环语句逐个处理resources数组中的全部资源
跟进它的this.loadBeanDefinitions((Resource)resource)
注意一下这里的new EncodedResource(resource)
这个适用于对资源路径进行编码的, 但是默认情况下是不进行操作的,我们可以跟进去看一下它的构造函数参数名就知道什么意思了
之后将可将location进行编码的对象返回到this.loadBeanDefinitions(new EncodedResource(resource))
继续跟进,下面这个loadBeanDefinitions就是真正解析加载资源的真正函数了
org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
传入一个xml文件数组string[],逐个处理xml文件string(路径);将配置文件资源路径放到resource[]里面,然后作为resource逐个处理加载。
org.springframework.util.xml.XmlValidationModeDetector#detectValidationMode
最终将得到的文件格式标识符返回到org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadDocument
执行org.springframework.beans.factory.xml.DocumentLoader#loadDocument
DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
生成一个创建DocumentBuilder的工厂
DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
通过工厂产生一个构建对象DocumentBuilder
return builder.parse(inputSource);
根据DocumentBuilder进行解析工作最后返回结果,这个构建过程没太大意义所以就不用到里面去看了
到这里得到一个Document对象,在这个对象里面详细分析规划好了xml文件里面的各种属性和对象,比如有哪些父元素子元素,元素里面有哪些标签,这些标签对应哪些属性。然后按照这些处理结果生成相应对象。
下下面这个Document对象返回到org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions并赋值给doc变量
这个doc对象里里面有我们在xml文件中定义的bean对象详细信息的node
然后我们将节点里面的bean信息读取出来然后进行bean标签的解析处理工作
小节点
此处获取xmL文件的document对象,这个解析过程是由documentLoader完成的,从String[] -string-Resource[]- resource, 最终开始将resource读取成一个document文档,根据文档的节点信息封装成一个个的BeanDefinition对象
进入this.registerBeanDefinitions(doc, resource)
doc.getDocumentElement()
获取document对象中的Bean节点详细信息
跟进this.doRegisterBeanDefinitions(doc.getDocumentElement())
解析生成对象的代码是this.parseBeanDefinitions(root, this.delegate)
我们这里跟进默认命名空间的解析代码this.parseDefaultElement(ele, delegate)
在这里判断标签类型然后使用解析器解析对应的标签
我们跟进非默认命名空间的解析代码delegate.parseCustomElement(ele)
看看
Bean标签解析流程
我们直接开看到默认命名空间解析Bean标签的函数
我们先跟进解析代码delegate.parseBeanDefinitionElement(ele)
在这里先看一下我们这里的Bean标签
在这里跟进this.parseBeanDefinitionElement(ele, beanName, containingBean)
经过上面的处理之后实际上我们的BeanDefinition已经完全把Bean标签的全部属性放到类内的属性里面了,此时的BeanDefinition也可以直接通过反射获取到对象了。
我们通过delegate.parseBeanDefinitionElement(ele)
得到了一个解析好Bean标签里面全部属性的BeanDefinition对象然后把她放到了一个BeanDefinitionHolder对象里去,这个BeanDefinitionHolder对象为bdHolder。
这个BeanDefinitionHolder是一个拓展别名的使用的BeanDefinition包装类, 直接当成是一个Beandefinition就行。
delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)
就是做了一些修饰工作, 在里面一般没什么太多操作,不需要关注。
下面开始注册了,我们继续执行下一步
跟进
下面我们跟进registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition())
看一下
org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
到了这里进入了一个DefaultListableBeanFactory类中, 在这个类里面有一个beanDefinitionMap属性, 这个属性用于存放BeanDefinition的类名和对象
而这里得到做就是将之前我们记录Bean标签各种属性的BeanDefinition对象放到这里进行处理, 把里面的name属性放到DefaultListableBeanFactory.beanDefinitionMap和BeanDefinitionNames中
生成实例化对象
最后会在refresh函数中执行一个finishBeanFactoryInitialization(beanFactory)
函数
这个函数会在最后执行beanFactory.preInstantiateSingletons();
这个函数会从this.beanDefinitionNames取出全部的name放到一个数组里面去,然后在后面使用一个while循环将这些name对应的类进行实例化
嗯.......就先到这吧,再看就受不了了。
最后一点话
说一下看了十几个小时的视频之后的感受吧,以一个菜鸟Wber手
的角度来说,如果是以学习漏洞为出发点其实不用把源码看的那么细(指的是整个框架的全部源码的详细运行流程), 我们应该关注漏洞相关代码
即可。
至于说抠细节的话出现漏洞点
的代码我们还是值得打点动调进去看看的,单个漏洞的话那也仅此而已了。但如果一个框架有多个漏洞那么我们可以再去了解漏洞点附近代码的处理逻辑
,属实不用把整个框架
的全部运行流程都拿下,因为涉及到的代码太多了,根本就是个无底洞。
为想学习Spring的一系列CVE所以我来看了Spring源码的整体运行流程。这两天只是学习了整体框架结构
和详细的服务启动流程
就已经头昏脑胀了,已结和看源码的目的本末倒置了,花了太多的时间和精力在这里(这个周末都搭在这里了),不过通过对源码分析的学习对Java的详细收获着实不小。