IOC流程解析-BeanFactory的创建

本次内容的重点是BeanFactory 的创建、BeanDefinition 的构建以及配置文件的解析、还有Schema 机制分析(这里要结合dubbo 的一点点内容,对于dubbo 有一定了解的小伙伴可以看下,没有的话建议跳过,后面看dubbo 的时候在回来看这个内容)。

Spring源码下载

在源码分析之前,有一个前提就是你能看到源码,你得本地有源码。那么我们先说下怎么下载spring 的源码,这里和其余框架不同,目前spring 的源码下载后的项目依赖管理不是maven 而是Gradle。这里关于Gradle 的下载教程我就不细说了,网上有很多可以自己去找找,我这里重点要说的是通过GitHub 将spring 的源码下载下来之后,遇到的一些常见问题。对了这里还要下载git啊。

  1. 源码下载下来后,需要在bin目录下,执行 gradlew.bat(建议命令行中执行)。
  2. 需要在源码根目录下右击出Git Bash Here,点击后出现命令窗口(这个git下载了才会有)后用命令配置自己的GitHub 用户名、邮箱、密码等信息,具体命令我放在最后吧。这样能解决”process ‘command’ ‘git’ finishend with non-zero exit value“这样的报错。
  3. 还要注意Gradle 的版本不好是最新版本,我这边的版本是5.6.4版本,还有spring 中的gradle.properties 的文件,需要主要其中的版本version 信息需要保持一致。
  4. 最后一个就是如果使用idea 遇到了编码问题的话,可以在Help => Edit Custom VM Options,点击后再文件中添加“-Dfile.encoding=UTF-8”。

这样将代码导入基本上就没有什么问题,需要注意的是spring 目前要需要jdk 是jdk11 以上版本。注释版源码的话可以访问我的CSDN资源下载:源码


BeanFactory的创建

源码下载用idea加载后,我们可以先创建一个测试类,这个可以方便于我们后面debug。

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath:applicationContext-cyclic.xml");

TestService1 testService1 = (TestService1) applicationContext.getBean("testService1");
TestService2 testService2 = (TestService2) applicationContext.getBean("testService2");
testService1.aTest();
testService2.aTest();
}

那么现在我们就可以启动后直接跟进ClassPathXmlApplicationContext 的构建里面,这个可以跟到的第一个代码点。这里只需要继续跟进核心方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {

// 1.如果已经有 ApplicationContext 并需要配置成父子关系,那么调用这个构造方法
super(parent);
// 2.根据提供的路径,处理成配置文件数组(以分号、逗号、空格、tab、换行符分割)
setConfigLocations(configLocations);

// refresh值默认为true
if (refresh) {
// *核心方法
refresh();
}
}

下面这个方法内容过多,可以直接根据文章目录点击到prepareRefresh方法,下面这段代码后面会一点一点解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public void refresh() throws BeansException, IllegalStateException {

// synchronized块锁(monitorenter --monitorexit)
// 不然 refresh() 还没结束,又来个启动或销毁容器的操作
// startupShutdownMonitor就是个空对象,锁
synchronized (this.startupShutdownMonitor) {


// Prepare this context for refreshing.
/*
【1.准备刷新】
(1) 设置容器的启动时间
(2) 设置活跃状态为true
(3) 设置关闭状态为false
(4) 获取Environment对象,校验配置文件
(5) 准备监听器和事件的集合对象,默认为空的set集合
*/
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
/*
【2. 初始化 新BeanFactory】重点!
(1)如果存在旧 BeanFactory,则销毁
(2)创建新的 BeanFactory(DefaluListbaleBeanFactory)
(3)解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(不初始化)
(4)返回新的 BeanFactory(DefaluListbaleBeanFactory)
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
// 【3. bean工厂前置操作】为BeanFactory配置容器特性
// 例如类加载器、表达式解析器、注册默认环境bean、后置管理器BeanPostProcessor
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
// 【4. bean工厂后置操作】此处为空方法,如果子类需要,自己去实现
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
//【5、调用bean工厂后置处理器】,开始调用我们自己实现的接口
// 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 回调方法
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
//【6. 注册bean后置处理器】只是注册,但是还不会调用
//逻辑:找出所有实现BeanPostProcessor接口的类,分类、排序、注册
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
//【7、初始化消息源】国际化问题i18n
initMessageSource(); // ===> 就是往factory加了个single bean

// Initialize event multicaster for this context.
//8、【初始化事件广播器】初始化自定义的事件监听多路广播器
// 如果需要发布事件,就调它的multicastEvent方法
// 把事件广播给listeners,其实就是起一个线程来处理,把Event扔给listener处理
// (可以通过 SimpleApplicationEventMulticaster的代码来验证)
initApplicationEventMulticaster(); // ===> 同样,加了个bean

// Initialize other special beans in specific context subclasses.
// 9、【刷新:拓展方法】这是个protected空方法,交给具体的子类来实现
// 可以在这里初始化一些特殊的 Bean
onRefresh();

// Check for listener beans and register them.
//10、【注册监听器】,监听器需要实现 ApplicationListener 接口
// 也就是扫描这些实现了接口的类,给他放进广播器的列表中
// 其实就是个观察者模式,广播器接到事件的调用时,去循环listeners列表,
// 挨个调它们的onApplicationEvent方法,把event扔给它们。
registerListeners(); // ===> 观察者模式

// Instantiate all remaining (non-lazy-init) singletons.
//11、 【实例化所有剩余的(非惰性初始化)单例】
// (1)初始化所有的 singleton beans,反射生成对象/填充
// (2)调用Bean的前置处理器和后置处理器
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
// 12、【结束refresh操作】
// 发布事件与清除上下文环境
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

进入核心方法,这里我们就需要一个一个方法看了,不过这次我们只看到BeanFactory 的创建和配置文件的解析。

prepareRefresh方法

首选我们可以看到的就是prepareRefresh 方法,这个方法其主要作用就是如下5点:

  1. 设置容器的启动时间
  2. 设置活跃状态为true
  3. 设置关闭状态为false
  4. 获取Environment对象,校验配置文件
  5. 准备监听器和事件的集合对象,默认为空的set集合

其实主要就是做一些准备工作,具体代码可以自己去翻一下,我这里就不看了。

obtainFreshBeanFactory-构建BeanFactory的方法

这方法是我们本次的重点,其主要做用也就是如下4点,这里我们需要一点点看。

1
2
3
4
5
6
7
8
/*
【2. 初始化 新BeanFactory】重点!
(1)如果存在旧 BeanFactory,则销毁
(2)创建新的 BeanFactory(DefaluListbaleBeanFactory)
(3)解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(不初始化)
(4)返回新的 BeanFactory(DefaluListbaleBeanFactory)
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

跟进该方法可以看到是主要调用refreshBeanFactory 和getBeanFactory 方法,这里的重点就是前者方法,后者只是将前者设置到的对象进行一个返回。

1
2
3
4
5
6
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 1.关闭旧的 BeanFactory (如果有),创建新的 BeanFactory
refreshBeanFactory();
// 2.返回刚创建的 BeanFactory(ConfigurableListableBeanFactory)
return getBeanFactory();
}

过度方法-refreshBeanFactory方法等等

跟进后我们来到的是AbstractRefreshableApplicationContext 类中,目前方法第一步就是先判断BeanFactory 是否存在,因为容器中只能存在一个BeanFactory 对象,存在则销毁,然后在创建一个全新的BeanFactory 对象,然后设置一些默认属性,然后就是继续方法调用,最后返回创建好的对象,这里的重点又是下一步方法loadBeanDefinitions。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Override
protected final void refreshBeanFactory() throws BeansException {
// 1.判断是否已经存在 BeanFactory,如果存在则先销毁、关闭该 BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 2.创建一个新的BeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 设置标识,用于 BeanFactory 的序列化
beanFactory.setSerializationId(getId());
// 设置 BeanFactory 的两个配置属性:(1)是否允许 Bean 覆盖 (2)是否允许循环引用
customizeBeanFactory(beanFactory);

/*
重点-->:加载 Bean 到 BeanFactory 中
1、通过BeanDefinitionReader解析xml为Document
2、将Document注册到BeanFactory 中(这时候只是bean的一些定义,还未初始化)
*/
// 3.加载 bean 定义
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

因为我这里使用的xml 方式进行的配置,所以我进入的是AbstractXmlApplicationContext 类,如果是注解的话进入的就是AnnotationConfigWebApplicationContext 类。

这里基本工作就是获取一些资源解析器,用于后面的解析xml 配置信息,重点跟进的地方任然是loadBeanDefinitions 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
// 1.为指定BeanFactory创建XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context's
// resource loading environment.
// 2.使用此上下文的资源加载环境配置 XmlBeanDefinitionReader
beanDefinitionReader.setEnvironment(this.getEnvironment());
// 资源加载器
beanDefinitionReader.setResourceLoader(this);
// 实体解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
// 校验,配置文件xsd和dtd头部
initBeanDefinitionReader(beanDefinitionReader);

// 3.加载 bean 定义 **===》
loadBeanDefinitions(beanDefinitionReader);
}

依然在AbstractXmlApplicationContext 类中,这里调用进入的方法都是同一个,只有获取资源的路径的方式不一样而已,还是需要跟进loadBeanDefinitions 方法,不过这次是AbstractBeanDefinitionReader 类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//下面的if分支一般会走第2个,无论走哪个,if里面的调的方法都是load
// 分支1::获取Resource

Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}

// 分支2:获取资源的路径
//前面解析出来的那些配置文件,classpath*:application.xml
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//重要!解析xml的结构正是在这里开端!!!
reader.loadBeanDefinitions(configLocations);
}
}

继续看就是还是跟进,这里的作用就是一个计数,这里会根据不同的配置文件重复调用loadBeanDefinitions。

这里下一步还是AbstractBeanDefinitionReader 类的loadBeanDefinitions 重载方法,不过这里任然是调用loadBeanDefinitions 方法。

最后的我们达到的是XmlBeanDefinitionReader 类的loadBeanDefinitions 方法,然后紧接着是调用其doLoadBeanDefinitions 方法。

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
// 计数,来统计所有xml里一共有多少个bean
int count = 0;
for (String location : locations) {
//继续进入loadBeanDefinitions,解析xml文件
count += loadBeanDefinitions(location);
}
//最终返回的classpath*:application.xml中配置bean的个数
return count;
}

在这里我们会将xml 使用inputSource和resource加载,并封装成Document 对象。然后传入registerBeanDefinitions 方法,这里还有一堆的异常分析捕捉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {

try {
// 1.根据inputSource和resource加载XML文件,并封装成Document
Document doc = doLoadDocument(inputSource, resource);

// 2.根据返回的Document注册Bean信息(对配置文件的解析,核心逻辑) ====>
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
}

到了这里我们就已经快接近真正的解析了,我们可以直接看下面代码中的第3步BeanDefinitionDocumentReader 对象的registerBeanDefinitions 方法调用。

1
2
3
4
5
6
7
8
9
10
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 1.获取documentReader,用于读取通过xml获得的document
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 2.获取当前xml文件(document)解析前,已经注册的BeanDefinition数目
int countBefore = getRegistry().getBeanDefinitionCount();
// 3.解析并注册当前配置文件中的BeanDefinition =====》进入
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 4.用当前注册的BeanDefinition数目减去之前注册的数目,返回该配置文件中注册BeanDefinition数目
return getRegistry().getBeanDefinitionCount() - countBefore;
}

进入后就是doRegisterBeanDefinitions 方法的调用。这里我们依旧是看重点parseBeanDefinitions 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected void doRegisterBeanDefinitions(Element root) {
/*
我们看名字就知道,BeanDefinitionParserDelegate 必定是一个重要的类,它负责解析 Bean 定义,
这里为什么要定义一个 parent? 看到后面就知道了,是递归问题,
因为 <beans /> 内部是可以定义 <beans /> 的,
所以这个方法的 root 其实不一定就是 xml 的根节点,也可以是嵌套在里面的 <beans /> 节点,从源码分析的角度,我们当做根节点就好了
*/
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);

// 1.校验root节点的命名空间是否为默认的命名空间(默认命名空间http://www.springframework.org/schema/beans)
if (this.delegate.isDefaultNamespace(root)) {
// 2.处理profile属性
//<beans ... profile="dev" />
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
//正常情况不会进入这里,具体代码就不展示了
}
}
}

/// 3.解析前处理, 留给子类实现
preProcessXml(root);
// 4.解析并注册bean定义, 核心解析方法,解析各种xml的标签,注册到BeanFactory!
parseBeanDefinitions(root, this.delegate); //====》
// 5.解析后处理, 留给子类实现
postProcessXml(root);

this.delegate = parent;
}

总结一下上面的过度方法:就是将原本的配置信息,转换封装为方便解读的对象,然后准备初始化一系类的解析器,并对返回对象进行封装等等。这里也看出xml 配置和注解配置,也就是这里会有一定的区别。

解析配置信息-parseBeanDefinitions方法

继续到了这一步,我们就算到了真正解析xml配置文件的地方了,这里还会涉及到后面的Schema 机制分析。

这里一共是两种解析方式:parseDefaultElement 默认命名空间默认节点的处理,比如常规的bean 标签等等、parseCustomElement 自定义命名空间自定义节点的处理,比如dubbo 提供的dubbo:service 标签等或者我们自定的标签,还有就是spring 本身的一些标签比如aop 标签等,这里其实也就是Schema 机制的解析方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* //解析beans下的xml节点,调用不同的方法来处理不同的节点
* @param root
* @param delegate
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 遍历root的子节点列表
// default namespace 涉及到的就四个标签 <import />、<alias />、<bean /> 和 <beans />
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
//下面有两个分支
if (delegate.isDefaultNamespace(ele)) {
// 1.1 默认命名空间节点的处理,例如: <bean id="test" class="" />
//分支1:代表解析标准元素 <import />、<alias />、<bean />、<beans /> 这几个
//标准节点
parseDefaultElement(ele, delegate);
}
else {
// 1.2 自定义命名空间节点的处理,例如:<context:component-scan/>、<aop:aspectj-autoproxy/>
//分支2:代表解析 <mvc />、<task />、<context />、<aop /> 、<component-scan />等
//特殊节点
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 2.自定义命名空间的处理
delegate.parseCustomElement(root);
}
}
默认命名空间的解析方式-parseDefaultElement

我们就先看parseDefaultElement 的解析方式,首选是对各种默认标签进行分类处理,我们只看bean 标签的处理方式就好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 标签解析
* @param ele
* @param delegate
*/
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 1.对import标签的处理
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
// <import resource="classpath:applicationContext-datasource.xml" />
importBeanDefinitionResource(ele);
}
// 2.对alias标签的处理
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
// <alias name="abc" alias="af"/>
processAliasRegistration(ele);
}
// 3.对bean标签的处理(最复杂最重要)
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
// 处理 <bean /> 标签定义 ====》
processBeanDefinition(ele, delegate);
}
// 4.对beans标签的处理
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// 如果碰到的是嵌套的 <beans /> 标签,需要递归
doRegisterBeanDefinitions(ele);
}
}
Bean标签的解析方法-processBeanDefinition

这里首先就是通过parseBeanDefinitionElement 方法解析标签中的所有内容,然后构建出BeanDefinition 这个对象,这个对象中存储的就是具体的配置信息,比如bean 的id、name、class 等属性,代码中虽然是BeanDefinitionHolder 但是它本身也就是对BeanDefinition 的一个封装而已。这里具体的解析标签的方法就详细看了,因为大部分跟市面上的解析方式都差不多,MyBatis 好像也就是一个套路。

这里的第二个重点就是registerBeanDefinition 注册方法,这个说直白一点就是为DefaultListableBeanFactory 对象,也就是往上面创建的BeanFactory 对象的beanDefinitionMap 集合添加对应的BeanDefinition 值,注意的是BeanDefinition 是一个ConcurrentHashMap 集合,并且这里是私有常量。

到这里Bean 的解析工作就算是结束了,同时也验证了上篇文章所有的是先创建了BeanFactory 对象,然后再解析构建的BeanDefinition 对象,最后又将BeanDefinition 对象存入了BeanFactory 对象中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 两步:1、解析,封装到对象的属性上去,
* 2、注册,注册到factory里去(其实就是俩Map存起来)
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {

// 1.bean节点具体的解析过程,属性,值等(bdHolder会包含一个Bean节点的所有属性,例如name、class、id) ====》
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 2.若存在默认标签的子节点下再有自定义属性,需要再次对自定义标签再进行解析(基本不用,不做深入解析)
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//3.注册Bean
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
// 4.注册完成后,发送事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}


Schema 机制分析

结合上面的解析配置信息的内容,我们在这里简单的说一下Schema 机制,这里需要用到dubbo 的一点知识。

下面就是dubbo 的常用配置xml,结合刚刚上面的内容,spring 启动的时候是只能解析下面的配置的bean 标签,对于dubbo 标签是没有解析的,但是上面内容也提到自义定命名空间和标签,也是可以通过parseCustomElement 方法来进行解析的,而这段解析内容也可以算是schema 机制的重点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

<!-- provider's application name, used for tracing dependency relationship -->
<dubbo:application name="demo-provider" />
<!-- <dubbo:metadata-report address="zookeeper://192.168.200.129:2181"/>-->

<dubbo:registry address="zookeeper://127.0.0.1:2181" />

<!--<dubbo:provider export="true" scope="remote" />-->

<!-- use dubbo protocol to export service on port 20880 -->
<dubbo:protocol name="dubbo" />

<!-- service implementation, as same as regular local bean -->
<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>

<!-- declare the service interface to be exported -->
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>

<bean id="myDemoService" class="org.apache.dubbo.demo.provider.MyDemoServiceImpl" />

<dubbo:service interface="org.apache.dubbo.demo.MyDemoService" ref="myDemoService"/>
</beans>

自定义命名空间和标签

我们先要了解的是怎样才算是自定义命名空间,其实我们将上面的xml 配置和spring 常规xml 配置对比,就能发现beans 标签中多了一个xmlns:dubbo 的属性,而xsi:schemaLocation 属性中也多出了一段关于dubbo 的dubbo.xsd 配置。

这两个东西具体作用就是:dubbo.xsd 文件规定了dubbo 标签的语法;而http://dubbo.apache.org/schema/dubbo 对应着META-INF 目录下spring.handlers 文件中配置的解析器类的路径。dubbo.xsd 文件也是在这个目录下的spring.schemas 文件导向的。

image-20220421232354561

自定义命名空间和标签的解析-parseCustomElement方法

既然知道了相关的语法和对象的解析器,那么我们就可以回到parseCustomElement 方法看看具体的解析内容,直接可以定位到BeanDefinitionParserDelegate 类的parseCustomElement 方法。

我们这里重点看两个地方,其一是:得到一个命名空间处理器,也就是resolve 方法、其二就是:开始解析,也就是parse 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
//解析节点的命名空间
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
//解析命名空间,得到一个命名空间处理器
//重点
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
//开始解析
//主线 重点
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

先看resolve 方法,这里我们其实要看的就是三个部分,初始化、缓存、返回,重点就是初始化。

这里可能还有人有疑惑,到底是怎么得到解析器的,其实就是spring 中的约定,约定好schema 就是通过spring.handlers 文件得到解析器,spring.schemas 文件规定语法,然后spring 项目中全部这个命名的文件内容,然后将信息存储下来。

既然我们上面已经得到了整个dubbo 的xml 配置文件,那么也就是得到了先关的beans 标签的值,这里就可以通过http://dubbo.apache.org/schema/dubbo 获取到相关的解析器,也就是DubboNamespaceHandler 对象,这个对象的顶层父类也就是NamespaceHandler 对象。

1
2
3
4
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;

然后就是DubboNamespaceHandler 对象的init 初始化方法。这里的初始化写法是固定的,又spring 提供,这可以看到是将各个不同标签的解析对象都创建,并封装成了DubboBeanDefinitionParser 对象,那么后面调用的话只是调用了DubboBeanDefinitionParser 对象的parse 方法,而不是对应的解析器的parse 方法,比如service 标签对应的ServiceBean 解析器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void init() {
/**
* 解析配置文件中<dubbo:xxx></dubbo:xxx> 相关的配置,并向容器中注册bean信息
*/
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}

DubboBeanDefinitionParser 对象的parse 方法的调用,这里则是通过不同的标签来构建出真正的解析器,比如service 标签对应的ServiceBean 解析器。

image-20220421234633341

而解析器的真正调用,也就是真正的标签解析,我们后面说。这里解析器逻辑只针对dubbo 啊,其余的不同框架都有一定的差异,但是大体流程就是这样的。


总结

本次的内容的重点就是BeanFactory 的创建流程、BeanDefinition 的构建和存储、缓存,配置信息的解析。结合之前的流程图,这也是完成对象实例化和初始化之前的准备工作,目前BeanFactory 的单例池是没有任何配置对象的,添加对象那是后面的事情。下一篇就是BeanFactoryPostProcessor 和BeanPostProcessor 的区别了。


附录Spring 源码分析系列文章

IOC

时间 文章
2022-03-09 Spring的基本概念和IOC流程的简述
2022-03-11 IOC流程解析-BeanFactory的创建
2022-03-14 IOC流程解析-BeanFactoyPostProcessor和BeanPostProcessor
2022-03-15 IOC流程解析-实例化和初始化
2022-03-17 IOC流程解析-循环依赖

AOP

时间 文章
2022-03-19 AOP流程源码分析-配置信息解析和代理对象创建
2022-03-20 AOP流程源码分析-请求调用全流程