spring 源码解析系列01--xml解析

前提说明:

相关文章,都是由本人的学习笔记转化,可能某个地方或者某一句话感到奇怪的地方,大家可以留言,我会逐一回答。

另外,spring源码阅读有一定的承上启下,如果没有前面的知识铺垫,很难从中间某一段源码解读去读懂相关关系的,其实 spring 这个框架,里面复杂的是一些类和接口的多继承,多实现的处理,还有一些比较绕人的递归逻辑,但是核心不离一些 processor, 如 BeanPostProcessor, BeanFactoryProcessor,  BeanDefinitionRedistry 等,这些都是 spring 的一些核心接口,实现他们就会可以自定义自己的扩展等。

源码解析中,为了能快速定位到源码方法位置,我使用 idea 的 copy reference 来复制完整的类名称和方法名,那样就可以通过快速查找(快速按两下 shift  找到方法),以核心方法 refresh() 为例,我会在文中以全限定类名#方法名(参数)的形式记录:org.springframework.context.support.AbstractApplicationContext#refresh

快速按两下 shift 键,粘贴类名#方法名,可以快速定位到具体方法,方便快速查看

 另外,分析的源码版本为 5.2.8

我们以最原始的方式,学习spring源码,最开始肯定是以 xml 形式的源码

其实一个最简单的spring框架,我们仅仅需要 context包即可

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
</dependency>

但是想打印日志,还是得依赖以下的包:

下面以简单的 xml 形式的配置跟一下源码:

student 类:

@Data
public class Student {
    private String username = "suganzhou";
}

spring.xml 文件

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"
    default-lazy-init="false">
    <bean id="student" class="com.su.bean.Student" />

</beans>

xml 构建一个spring 如下:

@Test
public void test1() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    Student student = applicationContext.getBean(Student.class);
    System.out.println(student.getUsername());
}

我们进入 ClassPathXmlApplicationContext 这个类:

进入核心方法:

org.springframework.context.support.AbstractApplicationContext#refresh

ClassPathXmlApplicationContext 是继承了这个类的,这个是抽象类,refresh() 是这个抽象类的方法,这是一种模板方法设计模式的体现,该方法是spring容器初始化的核心方法。是spring容器初始化的核心流程,是一个典型的父类模板设计模式的运用,根据不同的上下文对象,会调到不同的上下文对象子类方法中

核心上下文子类有:

* ClassPathXmlApplicationContext

* FileSystemXmlApplicationContext

* AnnotationConfigApplicationContext

* EmbeddedWebApplicationContext(springboot)

refresh 方法第一个重要方法:

obtainFreshBeanFactory(); obtain 为获得的意思

目的如下:

重要程度:5

* 1、创建BeanFactory对象

* 2、xml解析

* 传统标签解析:bean、import等

* 自定义标签解析 如:<context:component-scan base-package="com.xiangxue.jack"/>

* 自定义标签解析流程:

* a、根据当前解析标签的头信息找到对应的namespaceUri

* b、加载spring所有jar中的spring.handlers文件。并建立映射关系

* c、根据namespaceUri从映射关系中找到对应的实现了NamespaceHandler接口的类

* d、调用类的init方法,init方法是注册了各种自定义标签的解析类

* e、根据namespaceUri找到对应的解析类,然后调用paser方法完成标签解析

*

* 3、把解析出来的xml标签封装成BeanDefinition对象

核心我们关心的,还是最终把 xml 标签封装成 BeanDefinition 对象,后面初始化的相关条件参数都是从这个对象里面去取的

现在我们重要讲解 obtainFreshBeanFactory 这个方法:

org.springframework.context.support.AbstractApplicationContext#obtainFreshBeanFactory

obtainFreshBeanFactory 点进去:

首先调用的第一个方法是 refreshBeanFactory();, 这个在 AbstractApplicationContext 这个抽象类中的接口,故而然之,就是的让子类实现这个接口

但是我们使用 ctrl + alt 查看的时候,发现有两个实现,那么要怎么区分呢?

 我们首先看 clssPathXmlapplication 的继承关系:

 可以看到,clssPathXmlapplication 的 上几层父类,继承了 AbstractApplicationContext ,这个AbstractRefreshableApplicationContext 类就实现了 AbstractApplicationContext 的refreshBeanFactory方法

所以我们断点进去AbstractApplicationContext 的 refreshBeanFactory 方法:

spring 中,模板设计模式可谓是用得最多设计模式,用习惯了模板设计模式,感觉写什么都想用模板设计模式的感觉,比如设计 redis 的缓存,使用模板设计模式就能很好应用对公司的代码约束规范,这也是考验架构师的能力之一

模板设计模式大概如下:

1、父类定义抽象方法,同时父类定义核心方法,核心方法中去调用抽象方法

2、子类实现抽象方法

3、调用子类的父类核心方法,抽象方法层面执行的是子类的实现

这一段不重要,略过

 我们核心进入 customizeBeanFactory(beanFactory); 这个方法先,主要是根据参数设置是否允许循环依赖和 BeanDefinition 重载,默认没有配置的情况下,都不会进入这两个条件,但是这两个值默认都为true, 主要是为了禁用才存在这个设置:

 这两个参数要怎么配置进去呢?在讲解 BeanFactoryPostPossor 的时候再说,其实就是实现 BeanDefinitionRegistryPostProcessor 接口,实现 postProcessBeanFactory 接口中,拿到 beanFactory 进行设置进去就行,我们核心的,还是要看 loadBeanDefinitions(beanFactory); 这个方法

进入他的实现:org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)

 这里又产生了一个委托设计模式的存在,但是没什么作用,想看讲解可以看第二节课的13:38开始看。还是挺简单的,大概的意思就是,我实现了某接口,但是我的实现,是用其他人的接口实现来干的,就是说,A接口定义B方法,B类和C类都实现了B方法,但是C类虽然实现,但是他的实现是调用B类实现的B方法,相当于外包。我们主要看

loadBeanDefinitions(beanDefinitionReader) 这个方法

 继续

跟进这个 loadBeanDefinitions, 由于可能存在多个资源文件,所以这里使用了 for 循环

 核心还是再跟进 for 循环里面的 loadBeanDefinitions

 一路跟踪,到达 org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(java.lang.String, java.util.Set<org.springframework.core.io.Resource>)

 这里第一行的 ResourceLoader 拿到的是 ClassPathXmlApplicationContext 对象,从继承关系图中,ClassPathXmlApplicationContext 是有继承 ResourceLoader 的

 什么时候设置进去的呢?是在 org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory) 的时候设置进去的

我们核心看 int count = loadBeanDefinitions(resources); 这个方法:

org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource...)

进入 xml 的实现:

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

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

核心看这个方法:

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions

核心看 registerBeanDefinitions 这一行,根据解析出来的document对象,拿到里面的标签元素封装成BeanDefinition

进入 org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions 

继续走进核心方法 documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

核心代码到了:

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions

这里

 doRegisterBeanDefinitions 的作用,就是拿到标签,挨个解析各个节点,根据节点信息,再封装对应的 beanDefinition

主要看这个方法的 parseBeanDefinitions(root, this.delegate); 这一行

进入:

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions

 这里的解析,又分为默认标签解析,和自定义标签解析

解析默认标签:

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement

重点看解析 bean 标签的

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#processBeanDefinition

 跟进这个方法的第一行,来到 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)

 这个方法中,checkNameUniqueness(beanName, aliases, ele); 会检查名字是否唯一,我们进入解析的核心方法

AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, java.lang.String, org.springframework.beans.factory.config.BeanDefinition)

在这个方法中,会生成 BeanDefinition

 AbstractBeanDefinition bd = createBeanDefinition(className, parent); 这个代码中,会到 org.springframework.beans.factory.support.BeanDefinitionReaderUtils#createBeanDefinition 的时候 new 出来一个 GenericBeanDefinition 对象

 这里的,如果 className 不为空,要么就通过反射,设置到 beanClass 这个属性,要么就把 className 设置到 BeanClassName 这个属性里面去

spring 中,有N多个 BeanDefinition, 根据不同的功能产生不同的 BeanDefinition, 像 @service, @Compoment的注解,则为另外一个 AnnotationBeanDefinition, 每一个BeanDefinition都有多少差异,但是核心的都是 AbstractBeanDefinition 的对象,核心的内容都在这个 AbstractBeanDefinition 抽象对象里面

如果同时有注解,又有xml的话,注解的 bean会覆盖xml 的,不过这个条件是得在允许覆盖 BeanDefinition 的前提下

BeanDefinition 会贯穿整个 spring 的生命周期

重要属性如下:

 我们继续回到

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement 的其它方法

//创建GenericBeanDefinition对象
AbstractBeanDefinition bd = createBeanDefinition(className, parent);

//解析bean标签的属性,并把解析出来的属性设置到BeanDefinition对象中
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

//解析bean中的meta标签
parseMetaElements(ele, bd);
//解析bean中的lookup-method标签  重要程度:2,可看可不看
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析bean中的replaced-method标签  重要程度:2,可看可不看
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

//解析bean中的constructor-arg标签  重要程度:2,可看可不看
parseConstructorArgElements(ele, bd);
//解析bean中的property标签  重要程度:2,可看可不看
parsePropertyElements(ele, bd);
//可以不看,用不到
parseQualifierElements(ele, bd);

bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值