avatar

Dubbo源码分析之服务注册发布(二)

上一篇我们说到如何找切入点,也给大家伙稍微的提了一点关于Spring自定义标签解析的知识。这篇我们继续来进行Dubbo服务注册的讲解。该篇内容主要来谈谈Dubbo配置的解析。说到底,这一篇还是在分析一些Spring的代码,毕竟我们要知其所以然,不然突然就说是从这开始的,这不让人懵逼嘛。

前言

Fix Bug

  • 在上一篇在最后解释registerBeanDefinitionParser时有点错误,现已经改正,标签都是在DubboBeanDefinitionParserparse()方法中进行解析,解析操作和DubboBeanDefinitionParser构造方法的第一个参数稍稍有点关系,但是并不是在第一个参数的类中里面进行解析

回顾

在上一节,我们首先通过Spring官方文档了解到如果要解析自定义的xml标签需要哪些步骤,然后通过关键的一步:需要在/META-INF/spring.handlers/META-INF/spring.schemas中配置解析自定义标签的类以及schema的位置,找到了dubbo解析自定义标签的类:org.apache.dubbo.config.spring.schema.DubboNamespaceHandler,并且也稍微看了下里面的init()方法。我们发现啊,在init()方法中,有几个注册的DubboBeanDefinitionParser和其他的不同,如下图:

我们发现,其他的都是xxxConfig,就这三个是xxxBean。是有什么特殊的含义么?我们暂且不谈,先挖个坑,等后面再填,因为两个主线任务<dubbo:service><dubbo:refernce>都在里面,所以等后面分析时,我们还会再看回这里。

好了,回顾就到这里,开始今天的内容。

Dubbo中的配置实体解析

我们先聊聊Dubbo中配置实体,因为后续会用到。那么,有哪些呢?细心的大佬们肯定会发现,上面的图中不就有什么xxxConfig么?没错啦,这些就是Dubbo中的配置实体。我们随便点开一个看看其结构吧。就拿最近的ApplicationConfig来说吧,它内部的代码如下(因为篇幅太长,所以只贴一下属性):

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
public class ApplicationConfig extends AbstractConfig {

private static final long serialVersionUID = 5508512956753757169L;

/**
* Application name
*/
private String name;

/**
* The application version
*/
private String version;

/**
* Application owner
*/
private String owner;

/**
* Application's organization (BU)
*/
private String organization;

/**
* Architecture layer
*/
private String architecture;

/**
* Environment, e.g. dev, test or production
*/
private String environment;

/**
* Java compiler
*/
private String compiler;

/**
* The type of the log access
*/
private String logger;

/**
* Registry centers
*/
private List<RegistryConfig> registries;
private String registryIds;

/**
* Monitor center
*/
private MonitorConfig monitor;

/**
* Is default or not
*/
private Boolean isDefault;

/**
* Directory for saving thread dump
*/
private String dumpDirectory;

/**
* Whether to enable qos or not
*/
private Boolean qosEnable;

/**
* The qos port to listen
*/
private Integer qosPort;

/**
* Should we accept foreign ip or not?
*/
private Boolean qosAcceptForeignIp;

/**
* Customized parameters
*/
private Map<String, String> parameters;

/**
* Config the shutdown.wait
*/
private String shutwait;
//.... 省略方法 ....
}

可以看到我们<dubbo:application>标签中配置的属性都可以在上面找到。那我们在看一眼他的类图吧。

我们发现,貌似所有的Config都会继承这个AbstractConfig。在该类中,定义了一些公共的方法,比如toString()

在回顾里面,我们说有三个特殊的类,分别是ConfigCenterBean/ServiceBean/ReferenceBean。那么这三个又是如何接收配置的呢?我们分别来看看他们的类声明。

  • ConfigCenterBean

    1
    2
    3
    public class ConfigCenterBean extends ConfigCenterConfig implements ApplicationContextAware, DisposableBean, EnvironmentAware {
    // 类中代码省略.....
    }
  • ServiceBean

    1
    2
    3
    4
    public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
    ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
    ApplicationEventPublisherAware {
    }
  • ReferenceBean

    1
    2
    public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
    }

如上所见,他们每个都继承了一个xxxConfig类,xxxConfig类中和ApplicationConfig类一样,会在内部定义和对应xml标签配置的相关属性来接收配置信息,然后这些Config类都会继承AbstarctConfig。这一切似乎很明白了。

那么下一步,我们来说说Dubbo配置解析的流程

Dubbo的配置解析流程

关于Spring的xml解析,之前我有写过相关的文章,见[Spring源码深度解析读书笔记(2)](http://blog.zyzling.top/2018/12/20/Spring%E6%BA%90%E7%A0%81%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0(2)/,这次我们就不在浪费时间在这上面了,我们直接来分析Dubbo的配置解析,当然啦,里面还是会穿插Spring的知识在里面,毕竟底层还是用Spring来做解析。下面就让我们开始吧。

首先我们从DubboNamespaceHandlerinit()方法入手,也就是这张图

但从这段代码上来看,我们发现啊,它会调用registerBeanDefinitionParser()方法,为每个标签注册一个BeanDefinitionParser,那我们先从里面看起,先看看DubboBeanDefinitionParser是个什么鬼。

DubboBeanDefinitionParser

我们先看它的类声明。因为代码有300多行,如果全贴出来也不现实,所以我们只会贴一些关键的地方。

1
2
3
4
5
6
7
public class DubboBeanDefinitionParser implements BeanDefinitionParser {
//省略部分方法代码.....
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass, required);
}
}

抛却其他逻辑不讲,首先DubboBeanDefinitionParser是实现了Spring的BeanDefinitionParser接口,并且实现了parse()方法。我们可以看到在parse()方法里,会调用另外一个重载的parse()方法,并且会多传两个参数,一个是beanClass,一个是required,看上去这两个参数是成员属性,那么他是怎么定义和赋值的呢?见下面:

1
2
3
4
5
6
7
8
9
public class DubboBeanDefinitionParser implements BeanDefinitionParser {
private final Class<?> beanClass;
private final boolean required;

public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
this.beanClass = beanClass;
this.required = required;
}
}

从上面的代码,我们就可以很快的联想到在上面图片中的new DubboBeanDefinitionParser(ApplicationConfig.class, true)是干什么的了。那好,我们的DubboBeanDefinitionParser就先到此为止,放心,待会还会回来的~~

既然DubboBeanDefinitionParser我们有了了解,那么就看外层的registerBeanDefinitionParser()方法吧。这个方法是Spring中的NamespaceHandlerSupport这个类的,那我们所幸就先看看这个类吧。

NamespaceHandlerSupport

首先贴一下关键的地方代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
private final Map<String, BeanDefinitionParser> parsers = new HashMap();
private final Map<String, BeanDefinitionDecorator> decorators = new HashMap();
private final Map<String, BeanDefinitionDecorator> attributeDecorators = new HashMap();

public NamespaceHandlerSupport() {
}

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return this.findParserForElement(element, parserContext).parse(element, parserContext);
}

@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
return this.findDecoratorForNode(node, parserContext).decorate(node, definition, parserContext);
}
//隐含一个NamespaceHandler的init()方法
void init();
//省略其他代码.....
}

从上往下看,首先该类实现了NamespaceHandler接口,然后实现了其中的parse()decorate()方法,并且留了一个init()方法给NamespaceHandlerSupport的子类去实现,这也是我们DubboNamespaceHandler中的init()方法。

接着,内部定义了3个Map。这里我们只看第一个Map,也就是Map<String, BeanDefinitionParser> parsers = new HashMap().从变量名上看,这里应该存的是我们的xml解析类键值对,然后Key肯定是标签名,而Value则是具体对应的解析类。

那么NamespaceHandlerSupport全貌我们已经有所了解了。现在就来看看其内部的registerBeanDefinitionParser()方法。

registerBeanDefinitionParser(String,BeanDefinitionParser)

该方法内部就一行代码。如下:

1
2
3
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}

这里的this.parsers就是开头我们说的Map<String, BeanDefinitionParser>。也就是说,这里的register只是把我们创建的DubboBeanDefinitionParser和对应的标签名保存到Map中。

到这里,我们DubboNamespaceHandlerinit()方法明面上的代码都已经解释完毕了。接下来我们就先来静态分析一波解析的代码(这时候就要靠猜想了),然后在使用调试的方式验证下我们的猜想。

既然我们上面说,registerBeanDefinitionParser()只是把解析类放到Map中,那么肯定会有地方调用吧,而且我们Map的变量名是parses。然后我们在上面说过,这个抽象类实现了NamespaceHandler,并且也实现了其方法parse(),顾名思义,我们盲猜待会解析xml的时候,肯定会调用这个方法。既然这样,我们就来看看parse()方法吧。

parse(Element,ParserContext)

1
2
3
4
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}

这里分成2步:

  • findParserForElement()
  • parse()

我们先看findParserForElement()得到的是个什么鬼,才能继续往下看parse()方法

findParserForElement(Element,ParserContext)

1
2
3
4
5
6
7
8
9
10
11
12
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
//获取标签名
String localName = parserContext.getDelegate().getLocalName(element);
//从Map中根据标签名,得到我们在init()方法中注册的对应的BeanDefinitionParser
BeanDefinitionParser parser = this.parsers.get(localName);
//判空处理
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}

通过上面的代码,我们可以很清晰的知道,这个BeanDefinitionParser是从哪里获取的,并且值是什么了。这一切都串起来了。当然,这还只是我们的猜想,静态分析得到的结果,具体是不是这样,待会我们动态调试一下就可以立马知晓。

到了这,我们的parse(Element,ParserContext)方法的第二步parse()方法就可以继续看下去了。因为我们已经知道了通过findParserForElement(Element,ParserContext)得出来的是个什么鬼了。

既然知道是什么了,我们就直接来到其内部吧,也就是DubboBeanDefinitionParser#parse(Element, ParserContext)

DubboBeanDefinitionParser

上面说这个类的时候,我就说过,我们会回来的,毕竟前面都只是上层类,该类才是真正落地的实现类。

下面就让我们来看看它的parse(Element element, ParserContext parserContext) 方法

parse(Element,ParserContext)

1
2
3
4
5
6
7
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
//beanClass就是我们new DubboBeanDefinitionParser时传的Class。
//required我们这里虽然不能知道其表达什么含义,
//但是从我们创建的DubboBeanDefinitionParser来看,它值都是true,除了reference标签
return parse(element, parserContext, beanClass, required);
}

也是调用内部的重载的方法parse(element, parserContext, beanClass, required)

parse(Element,ParserContext,Class<?>,boolean)

鉴于该方法篇幅有点长,并且都是一些对配置的解析,所以我们会省略其解析部分,并且分段来展示。然后又因为在某些地方,因为都是获取的父类或者接口类,所以我们不知道其具体实现类是什么,这时候就需要借助动态断点调试的方式来分析。

第一部分 准备
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
/**
* element:dom解析每个标签生成的对象
* ParserContext:上下文
* beanClass:在new DubboBeanDefinitionParser时传递的参数。例如,ApplicationConfig.class/ServiceBean.class等
* required:是否必需,除了beanClass=ReferenceBean时为false,其余都是true
*/
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
//创建一个RootBeanDefinition对象,设置其属性
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
//从标签中获取名为id的属性值
String id = element.getAttribute("id");
//如果id为空,并且是必须的,就进if体。通过构造方法,我们就知道了,除了reference标签外,其余的required都是true
if (StringUtils.isEmpty(id) && required) {
//获取名为name的属性值
String generatedBeanName = element.getAttribute("name");
//如果name是空的。这是就分两种情况:
//第一种是如果是在解析Protocol标签,那么就取dubbo。
//第二种就是普遍用法,尝试取interface属性值
if (StringUtils.isEmpty(generatedBeanName)) {
if (ProtocolConfig.class.equals(beanClass)) {
generatedBeanName = "dubbo";
} else {
generatedBeanName = element.getAttribute("interface");
}
}
//到这里,如果上面得到的值还是空,就会取beanClass的全限定类名
if (StringUtils.isEmpty(generatedBeanName)) {
generatedBeanName = beanClass.getName();
}
//从这里看,上面的各种操作,是对于id为空的情况下的兜底值方案。
id = generatedBeanName;
//如果id已经存在,那么这个是在哪里存在,我们暂时不管,因为下面的getRegistry()获取的是一个接口。
//所以我们静态分析是不好找他的具体实现类的,不过没关系,待会动态调试一下就ok了。
//再说回来,如果发现id已经存在了,那么我就只能重命名这个id了。最简单的方法就是在后面加个数字。
//所以下面就这样做了。
int counter = 2;
while (parserContext.getRegistry().containsBeanDefinition(id)) {
id = generatedBeanName + (counter++);
}
}
//到了这,基本上id不为空,并且length也>0了。也就是下面的if体,绝大多数情况,都是必进的。
if (id != null && id.length() > 0) {
//这里再次判断了下是否已经存在了相同的id,因为有可能从标签中取到的id就不为空。
//但是从这里的异常信息来看,貌似这个id是一个bean的id。那么这个bean是什么,现在我们不知道。
//所以只能继续往下面看了。
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " + id);
}
//这里好像就是往一个地方注册一个bean耶,传id和一个RootBeanDefinition。
//那么是不是上面的那个bean指的就是RootBeanDefinition呢?到时候我们调试下。
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
//保存一下id属性到beanDefinition中。保存到哪,怎么保存的我们就不分析了。因为是Spring的内容了。
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
//以下代码省略......
}

从第一部分代码来看,无非就是读取标签中的id属性值。然后以id值为key,把RootBeanDefinition注册到一个地方。注意哦,我们是一个标签一个配置实体的,从DubboNamespaceHandler#init()方法中也可以看到。

第二部分 兼容之前的代码?
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
/**
* element:dom解析每个标签生成的对象
* ParserContext:上下文
* beanClass:在new DubboBeanDefinitionParser时传递的参数。例如,ApplicationConfig.class/ServiceBean.class等
* required:是否必需,除了beanClass=ReferenceBean时为false,其余都是true
*/
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
//------ 以上代码省略 ------
//各种beanClass的处理
if (ProtocolConfig.class.equals(beanClass)) {
//如果是Protocol,就遍历所有的BeanDefinitionName。
//到这里,我们猜想,可能在上面register我们的bean时,同时也会存我们的id。
//所以这里应该获取到的是全部标签对应的id
for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
//根据name获取对应的beanDefinition
BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
//获取beanDefinition里面的Protocol属性
PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
//下面的方法不清楚是干啥的。希望晓得大佬指点一哈。
if (property != null) {
Object value = property.getValue();
if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
}
}
}
} else if (ServiceBean.class.equals(beanClass)) {
//ServiceBean的处理。
//获取class属性值
String className = element.getAttribute("class");
//配了class属性的话,就进if体。不过貌似在官网貌似找不到这个class属性的说明。
//但是看xsd的话,对class的说明是服务实现类的全类名,应该是之前dubbo版本遗留的代码吧。
//不知道说的对不对,希望知道的大佬可以指点一下。
if (className != null && className.length() > 0) {
//看下面的实现,貌似是手动把实现类注册到Spring?
RootBeanDefinition classDefinition = new RootBeanDefinition();
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
parseProperties(element.getChildNodes(), classDefinition);
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
} else if (ProviderConfig.class.equals(beanClass)) {
//ProviderConfig的处理
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
} else if (ConsumerConfig.class.equals(beanClass)) {
//ConsumerConfig的处理
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}

//------- 以下代码省略 -------
}

这部分我感觉是为了兼容之前版本的代码,为什么这么说呢?拿ServiceBean这个if分支来说,class这个属性值,在官方文档上都没有找到说明,还有ProviderConfigConsumerConfig,这两个对应的标签是<dubbo:provider><dubbo:consumer>,但是我们现在都是用<dubbo:service>来暴露服务,用<dubbo:reference>来引用服务。这里让我不解,望大佬们指教。

第三部分 关键属性解析
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* element:dom解析每个标签生成的对象
* ParserContext:上下文
* beanClass:在new DubboBeanDefinitionParser时传递的参数。例如,ApplicationConfig.class/ServiceBean.class等
* required:是否必需,除了beanClass=ReferenceBean时为false,其余都是true
*/
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
//----- 以上代码省略 -----
//存放处理过的属性,为什么这么说呢?在后面就知道了
Set<String> props = new HashSet<>();
ManagedMap parameters = null;
//遍历bean中的方法。也就是说,如果现在解析的是<dubbo:application>,那么遍历的是ApplicationConfig
for (Method setter : beanClass.getMethods()) {
//获取方法名
String name = setter.getName();
//只取set方法
if (name.length() > 3 && name.startsWith("set")
&& Modifier.isPublic(setter.getModifiers())
&& setter.getParameterTypes().length == 1) {
//获取参数类型
Class<?> type = setter.getParameterTypes()[0];
//截取属性名。假设是setName()方法,那么这里的值就是name
String beanProperty = name.substring(3, 4).toLowerCase() + name.substring(4);
//假设方法是setNameStr(),那么结果是name-str
String property = StringUtils.camelToSplitName(beanProperty, "-");
//把属性名加入到Set中
props.add(property);

Method getter = null;
try {
//获取对应的get方法
getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e) {
//如果上面的没有找到,说明可能属性值是boolean,就使用下面的这种方式取一下。
try {
getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e2) {
}
}
//校验get方法的合法性。不合法就continue。
if (getter == null
|| !Modifier.isPublic(getter.getModifiers())
|| !type.equals(getter.getReturnType())) {
continue;
}
//如果有setParameters()方法,就走下面的解析。
if ("parameters".equals(property)) {
parameters = parseParameters(element.getChildNodes(), beanDefinition);
} else if ("methods".equals(property)) {
//setMethods()方法,走下面的解析
parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
} else if ("arguments".equals(property)) {
//setArguments()方法,走下面的解析
parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
} else {
//其他setXXX(),都是走下面
//从标签中获取对应的属性值。
//假设配置是<dubbo:application name="xxxx">
//那么当method是setName()时,就会截取name,从标签中取出xxxx
String value = element.getAttribute(property);
if (value != null) {
//去除前后空格
value = value.trim();
if (value.length() > 0) {
//如果属性名是registry,并且值是'N/A'.就手动new一个RegistryConfig
//设置地址为N/A,并且放到beanDefinition中
if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress(RegistryConfig.NO_AVAILABLE);
beanDefinition.getPropertyValues().addPropertyValue(beanProperty, registryConfig);
} else if ("provider".equals(property) || "registry".equals(property) || ("protocol".equals(property) && ServiceBean.class.equals(beanClass))) {
//如果是setProvider,或者是setRegistry,或者是ServiceBean的setProtocol方法,就把属性都拼上Ids。例如registryIds/providerIds/protocalIds
beanDefinition.getPropertyValues().addPropertyValue(beanProperty + "Ids", value);
} else {
//其他属性的解析
Object reference;
//如果是基本数据类型以及对应的包装类和String类型
if (isPrimitive(type)) {
//如果属性是如下值,也就是默认值时,value置为null.
//否则就把值赋给reference。
if ("async".equals(property) && "false".equals(value)
|| "timeout".equals(property) && "0".equals(value)
|| "delay".equals(property) && "0".equals(value)
|| "version".equals(property) && "0.0.0".equals(value)
|| "stat".equals(property) && "-1".equals(value)
|| "reliable".equals(property) && "false".equals(value)) {

value = null;
}
reference = value;
} else if ("onreturn".equals(property)) {
int index = value.lastIndexOf(".");
String returnRef = value.substring(0, index);
String returnMethod = value.substring(index + 1);
reference = new RuntimeBeanReference(returnRef);
beanDefinition.getPropertyValues().addPropertyValue("onreturnMethod", returnMethod);
} else if ("onthrow".equals(property)) {
int index = value.lastIndexOf(".");
String throwRef = value.substring(0, index);
String throwMethod = value.substring(index + 1);
reference = new RuntimeBeanReference(throwRef);
beanDefinition.getPropertyValues().addPropertyValue("onthrowMethod", throwMethod);
} else if ("oninvoke".equals(property)) {
int index = value.lastIndexOf(".");
String invokeRef = value.substring(0, index);
String invokeRefMethod = value.substring(index + 1);
reference = new RuntimeBeanReference(invokeRef);
beanDefinition.getPropertyValues().addPropertyValue("oninvokeMethod", invokeRefMethod);
} else {
//解析<dubbo:service>中的ref属性。从Spring中通过属性值获取对应的BeanDefinition。
if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) {
BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value);
//如果不是单例,就抛异常。
if (!refBean.isSingleton()) {
throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>");
}
}
reference = new RuntimeBeanReference(value);
}
//把reference的值,放到beanDefinition中
beanDefinition.getPropertyValues().addPropertyValue(beanProperty, reference);
}
}
}
}
}
}
//----- 以下代码省略 ------
}

从这部分看,解析xml到实体的套路我们已经知道了。他是通过set方法把xml属性设置到实体中。然后放到对应的beanDefinition中。

第四部分 其他属性解析

这也是该方法的最后一部分,代码如下:

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
/**
* element:dom解析每个标签生成的对象
* ParserContext:上下文
* beanClass:在new DubboBeanDefinitionParser时传递的参数。例如,ApplicationConfig.class/ServiceBean.class等
* required:是否必需,除了beanClass=ReferenceBean时为false,其余都是true
*/
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
//------- 以上代码省略 --------
//获取所有属性
NamedNodeMap attributes = element.getAttributes();
int len = attributes.getLength();
//循环取所有属性
for (int i = 0; i < len; i++) {
//获取属性名
Node node = attributes.item(i);
String name = node.getLocalName();
//如果在上面没有被处理。则统一在这里处理。
if (!props.contains(name)) {
if (parameters == null) {
parameters = new ManagedMap();
}
String value = node.getNodeValue();
//把剩下的属性和对应的值,存到parameters(Map)中。
parameters.put(name, new TypedStringValue(value, String.class));
}
}
//把其他属性也放到beanDefinition中。key为parameters。
if (parameters != null) {
beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
}
//最后返回这个beanDefinition
return beanDefinition;
}

上面这部分代码就是把其他属性,作为一个parameters放到beanDefinition中。到此,整个parse(Element,ParserContext,Class<?>,boolean)也就在我们的猜想中静态分析完了,其中有些地方不是很明确,也可能有些地方有错误,还望各位大佬能够不吝指教一番。

下面就让我们用动态调试的方式来验证我们的猜想吧。

动态调试Dubbo配置解析流程

动态调试,在哪下断点是关键,既然我们说最后无论如何都会到我们的DubboBeanDefinitionParser#parse(Element,ParserContext,Class<?>,boolean)方法,那我们就在首行下个断点,让应用跑起来。

现在我们通过断点调试来确定下下面的问题。

  • 是否可以在我们加的断点中断下来。
  • parserContext.getRegistry()返回的具体实现类是啥?
  • 最后解析的结果去哪了?

第一个问题 是否可以断下来

答案是肯定的,如图:

第二个问题 parserContext.getRegistry()返回的实现类是什么?

我们来到该方法调用处,截图如下:

看上面的图来说,具体的实现类是DefaultListableBeanFactory,既然知道了是这个类,那么有地方就得说说了。我们继续回头看看这段代码,这段代码在第一部分 准备这一节里面

1
2
3
4
5
6
7
8
9
if (id != null && id.length() > 0) {
//这里也可以继续说说,不过先说下面的。再来看这个containsBeanDefinition()
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " + id);
}
//这里可以再继续说说。为什么要说呢?因为在后续分析的时候会用到,所以有必要说一下
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}

DefaultListableBeanFactory#registerBeanDefinition(String, BeanDefinition)

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
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {

Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");

if (beanDefinition instanceof AbstractBeanDefinition) {
//省略部分代码...
}
//旧的definition
BeanDefinition oldBeanDefinition;
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
//省略oldBeanDefinition不为null的处理
else {
if (hasBeanCreationStarted()) {
//省略部分代码
}else {
//beanName就是传进来的id,这里就是保存我们bean
this.beanDefinitionMap.put(beanName, beanDefinition);
//保存id
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}

if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}

所谓的registerBeanDefinition就是在把bean保存在DefaultListableBeanFactory的一个成员属性中,也就是一个Map中。

那么我们看一下parserContext.getRegistry().containsBeanDefinition(id)

DefaultListableBeanFactory#containsBeanDefinition(String)

1
2
3
4
5
@Override
public boolean containsBeanDefinition(String beanName) {
Assert.notNull(beanName, "Bean name must not be null");
return this.beanDefinitionMap.containsKey(beanName);
}

上面的代码清晰可见,当我们调用该方法时,会去看看Map中是否有该key。

最后一个问题,解析的配置存哪了?

没错,最后的解析后的配置就放在了我们的DefaultListableBeanFactory的一个Map成员属性中,也就是beanDefinitionMap

总结

这一篇篇幅较上一篇长很多,大致说了如下几个事

  • Dubbo中配置实体的结构
  • Dubbo的配置是如何解析的,也就是解析流程。
  • 动态调试,验证猜想和确定了parserContext.getRegistry()的具体实现类

好了,这一篇就到这里了,有错误还望大佬们不吝指教,最后,五一劳动节快乐~咱们啊王者峡谷见~


评论