学习Spring源码分析

学习Spring源码分析

​ 经过周末两天对源码的学习只能说千万别扣细节,那简直就是一个无底洞,现在已经被劝退了。这个文章就是一些资料和零散的学习笔记,属实没时间精力去完善整理这两天学习的东西了。

B站学习链接

一些知识流程图

image-20220410234748210

image-20220410234813464

image-20220410234825293

image-20220410235618533

image-20220410235637400

image-20220410235654524

image-20220410235716770

Main函数

我们直接在整个项目的第一步main函数的第一句开始打断点进去逐步调试看源码的整个运行流程

image-20220410140024331

一些重要类的介绍

这里并不齐全,只是在学习的过程中几乎全凭心情捡了几个比较重要的类出来查找了一些资料, 在这里当做一个参考吧。

DefaultListableBeanFactory

image-20220409182931857

父类的作用:

  1. doGetBean 父子容器查找对象方法
  2. ListableBeanFactory 把全部bean对象枚举出来
  3. ConfigurableBeanFactory 用于获取和设置Bean的属性值

DefaultSingletonBeanRegistry

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

可以看到这个单例的Bean注册类使用了三级缓存模式

image-20220410123129863

三级缓存的是匿名内部类或者匿名表达式

详细介绍

原文中的英文注释分别阐述了以下内容(一个段落用一个小标): ⑴、类DefaultSingletonBeanRegistry实现了接口SingletonBeanRegistry,是通用注册表的基础实现类: ①、它允许注册bean对象实例(这些对象实例都是单例模式)。 ②、已经注册的bean对象实例是注册表的所有调用者共享的(所以特别需要注意线程安全)。 ③、可以通过bean名称获得目标bean对象实例。 ⑵、类DefaultSingletonBeanRegistry还支持类DisposableBean对象实例的注册(在关闭注册表时销毁),该实例可能与已注册的bean对象实例相对应。bean对象实例之间的依赖关系也可以注册,以此控制销毁bean对象实例的顺序。 ⑶、类DefaultSingletonBeanRegistry主要作为实现接口BeanFactory的基类,它分解了单例bean对象实例的常见管理(比如注册,获取等等,但不包含创建)。 ①、另一个接口ConfigurableBeanFactory扩展了接口SingletonBeanRegistry。 ⑷、类DefaultSingletonBeanRegistry与类AbstractBeanFactory和类DefaultListableBeanFactory(继承自类DefaultSingletonBeanRegistry)相比,既不涉及bean对象实例概念的定义也不涉及bean对象实例的创建过程,还可以用作委托的嵌套助手(涉及Java设计模式)。 介绍参考文章

执行顺序流程

image-20220410140024331

image-20220410124015342

image-20220410124028971

接着一堆父类的初始化操作, 在这个操作操作过程中就进行了一些属性的初始化赋值操作, 所以后面的时候使用一些对象不用奇怪。

执行了super父类的初始化之后设置配置文件属性,最后执行refresh操作

image-20220410124533157

refresh函数

这两天可以说就是学习了这一个函数,整个函数很关键,项目所需de全部Bean初始化操作都在这里完成了,也可以说这两天就只是学习了这个函数。

image-20220410122850361

image-20220410122926779

image-20220410122953233

refresh的天书时序图:

AbstractApplicationContext_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)

image-20220410131536126

在这个beanDefinitionReader对xml中的Bean对象进行解析.

之间的三个set函数设置beanDefinitionReader对象属性:

1.设置系统的一些环境变量Enviroment
2.设置资源加载器
3.设置实体处理器,用于将xml文件的Bean标签实例化,但是这里不会进行处理,只是指定一个处理器而已

实体处理器

进入处理器的构造函数:

org.springframework.beans.factory.xml.ResourceEntityResolver#ResourceEntityResolver

image-20220410132331065

org.springframework.beans.factory.xml.PluggableSchemaResolver#PluggableSchemaResolver(java.lang.ClassLoader)

image-20220410132439168

这里这个文件就是本地xml文件规范的资源文件,在xml文件中定义有一个规范文件的网络地址,但是如果断网离线的时候我们加载不了那个规范文件,这时就会加载本地的这个文件作为解析规范对xml文件进行解析。

设置完类处理器后对这个beanDefinitionReader传入initBeanDefinitionReader函数中设置是否对配置文件进行验证,默认为true

解析xml文件

读取配置文件

最后将beanDefinitionReader传入loadBeanDefinitions函数进行xml文件的解析

image-20220410134750407

这里的getConfigLocations对应的变量在执行refresh之前就已经执行set函数设置好了:

image-20220410134900548

在AbstractBeanDefinitionReader#loadBeanDefinitions执行循环处理configLocations中的每一个xml资源文件

image-20220410135333124

跟进里面的this.loadBeanDefinitions(location)函数

image-20220410135409027

再次调用了类内函数,跟进this.loadBeanDefinitions(location, (Set)null)

image-20220410135526020

getResources(location)中获得符合我们设置的xml文件名ANT表达式的资源文件地址, 放到resources数组中

image-20220410140547102

this.loadBeanDefinitions(resources)遍历资源路径数组

image-20220410140817213

在这里使用循环语句逐个处理resources数组中的全部资源

跟进它的this.loadBeanDefinitions((Resource)resource)

image-20220410141219712

注意一下这里的new EncodedResource(resource)这个适用于对资源路径进行编码的, 但是默认情况下是不进行操作的,我们可以跟进去看一下它的构造函数参数名就知道什么意思了

image-20220410141347429

image-20220410141340556

之后将可将location进行编码的对象返回到this.loadBeanDefinitions(new EncodedResource(resource))

继续跟进,下面这个loadBeanDefinitions就是真正解析加载资源的真正函数了

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)

image-20220410141636877

传入一个xml文件数组string[],逐个处理xml文件string(路径);将配置文件资源路径放到resource[]里面,然后作为resource逐个处理加载。

image-20220410141856235

image-20220410142200615

image-20220410142300503

image-20220410142359312

image-20220410142521258

image-20220410142549854

org.springframework.util.xml.XmlValidationModeDetector#detectValidationMode

image-20220410142902598

最终将得到的文件格式标识符返回到org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadDocument

image-20220410143224739

执行org.springframework.beans.factory.xml.DocumentLoader#loadDocument

image-20220410143308447

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变量

image-20220410144238276

这个doc对象里里面有我们在xml文件中定义的bean对象详细信息的node

然后我们将节点里面的bean信息读取出来然后进行bean标签的解析处理工作

image-20220410144448280

小节点

image-20220410144238276

此处获取xmL文件的document对象,这个解析过程是由documentLoader完成的,从String[] -string-Resource[]- resource, 最终开始将resource读取成一个document文档,根据文档的节点信息封装成一个个的BeanDefinition对象

进入this.registerBeanDefinitions(doc, resource)

image-20220410144939893

image-20220410145112423

doc.getDocumentElement()获取document对象中的Bean节点详细信息

image-20220410145121170

跟进this.doRegisterBeanDefinitions(doc.getDocumentElement())

image-20220410145725890

解析生成对象的代码是this.parseBeanDefinitions(root, this.delegate)

image-20220410150319505

我们这里跟进默认命名空间的解析代码this.parseDefaultElement(ele, delegate)

image-20220410150519724

在这里判断标签类型然后使用解析器解析对应的标签

我们跟进非默认命名空间的解析代码delegate.parseCustomElement(ele)看看

image-20220410150803278

image-20220410150824627

Bean标签解析流程

我们直接开看到默认命名空间解析Bean标签的函数

image-20220410151118583

我们先跟进解析代码delegate.parseBeanDefinitionElement(ele)

image-20220410151308257

在这里先看一下我们这里的Bean标签

image-20220410151353006

image-20220410151527582

image-20220410151756501

在这里跟进this.parseBeanDefinitionElement(ele, beanName, containingBean)

image-20220410151920004

image-20220410152019640

image-20220410152554563

image-20220410153425111

经过上面的处理之后实际上我们的BeanDefinition已经完全把Bean标签的全部属性放到类内的属性里面了,此时的BeanDefinition也可以直接通过反射获取到对象了。

image-20220410155337884

我们通过delegate.parseBeanDefinitionElement(ele)得到了一个解析好Bean标签里面全部属性的BeanDefinition对象然后把她放到了一个BeanDefinitionHolder对象里去,这个BeanDefinitionHolder对象为bdHolder。

这个BeanDefinitionHolder是一个拓展别名的使用的BeanDefinition包装类, 直接当成是一个Beandefinition就行。

delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)就是做了一些修饰工作, 在里面一般没什么太多操作,不需要关注。

下面开始注册了,我们继续执行下一步

image-20220410155206839

跟进

image-20220410155938046

下面我们跟进registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition())看一下

org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition

image-20220410160446630

image-20220410160739255

到了这里进入了一个DefaultListableBeanFactory类中, 在这个类里面有一个beanDefinitionMap属性, 这个属性用于存放BeanDefinition的类名和对象

而这里得到做就是将之前我们记录Bean标签各种属性的BeanDefinition对象放到这里进行处理, 把里面的name属性放到DefaultListableBeanFactory.beanDefinitionMap和BeanDefinitionNames中

生成实例化对象

最后会在refresh函数中执行一个finishBeanFactoryInitialization(beanFactory)函数

这个函数会在最后执行beanFactory.preInstantiateSingletons();

image-20220410161832214

image-20220410161907452

这个函数会从this.beanDefinitionNames取出全部的name放到一个数组里面去,然后在后面使用一个while循环将这些name对应的类进行实例化

嗯.......就先到这吧,再看就受不了了。

最后一点话

​ 说一下看了十几个小时的视频之后的感受吧,以一个菜鸟Wber手的角度来说,如果是以学习漏洞为出发点其实不用把源码看的那么细(指的是整个框架的全部源码的详细运行流程), 我们应该关注漏洞相关代码即可。

​ 至于说抠细节的话出现漏洞点的代码我们还是值得打点动调进去看看的,单个漏洞的话那也仅此而已了。但如果一个框架有多个漏洞那么我们可以再去了解漏洞点附近代码的处理逻辑,属实不用把整个框架的全部运行流程都拿下,因为涉及到的代码太多了,根本就是个无底洞。

​ 为想学习Spring的一系列CVE所以我来看了Spring源码的整体运行流程。这两天只是学习了整体框架结构详细的服务启动流程就已经头昏脑胀了,已结和看源码的目的本末倒置了,花了太多的时间和精力在这里(这个周末都搭在这里了),不过通过对源码分析的学习对Java的详细收获着实不小。

posted @ 2022-04-25 12:52  h0cksr  阅读(80)  评论(0编辑  收藏  举报