avatar

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

前言

前面我们已经对Dubbo核心的SPI机制做了分析,那么从这一篇起,我们来开始分析Dubbo的一些功能实现。

我打算先把服务注册和客户端调用这部分逻辑给理清楚,而后其他的功能,则留到后面作为番外。emm,之前并发貌似也是这么说的。

因为服务注册和客户端调用这部分逻辑代码量很大,逻辑虽然简单,但是过程曲折。为了能够理清楚调用关系,所以可能会分成多篇来讲解,而且可能每篇长短不一。又因为Dubbo是通过URL驱动,所以它里面会有大量的拼凑参数的代码,而这些代码我们没有必要去跟,所以会配合断点调试来进行。

今天我们来学习服务注册的第一篇。今天先来一点简单的——找入口。

开始进入正题。注意:咱Dubbo的版本是2.7.2

找入口

源码分析找入口很重要,我们以Xml配置为例来找入口。首先抛开所有的逻辑,先从配置入手。为什么要用配置作为切入点呢?因为我们使用Dubbo前,必须要正确的进行xml配置后,Dubbo才能被我们所用,所以不妨试试从这方面下手。

我们知道Dubbo是依赖Spring的,而我们在配置服务提供端时,还用了Spring的<bean>标签,然后我们还会用Dubbo的一些标签,比如<dubbo:service><dubbo:registry>等。我们知道<bean>是属于Spring内部的标签,而我们的<dubbo:xxx>是Dubbo自己定义的,并且我们还会在xml中可以看到Dubbo定义的xml约束以及命名空间。如下图:

那么既然Dubbo自己定义了标签,肯定会有个解析的地方,并且也要以某种方式告诉Spring,碰到了这些标签,应该怎么去解析。也就是说,我们只要找到Dubbo告诉Spring怎么解析这些标签的地方,我们也就找到了一个入口点。

问题是,在哪可以找到呢?别慌,我们看一眼Spring的官方文档,里面肯定有写。地址都帮你们找好了,因为这部分是最基础的部分,也就是在Spring Framework中,Spring也没有动过他们,所以我们随便找个版本看看就行了,这里我找的是它的最新版5.2.5地址在这里,截图如下:

看不懂没关系。下面来个中文翻译对照版的。

如果你想要创建新的XML配置扩展,请执行以下操作:

  1. 编写一个xml schema来描述你的自定义标签。(也就是需要定义一个xsd来约束你这些标签的定义)
  2. 编写一个NamespaceHandler的实现,当Spring解析配置文件时遇到你自定义的标签时会调用该类的方法进行解析。一般我们会继承他的实现类NamespaceHandlerSupport,该接口的方法如下
    • init():在Spring解析配置之前被调用,一般用于初始化标签解析的操作。
    • BeanDefinition parse(Element, ParserContext):当碰到我们自定义的标签时,会调用该方法进行解析。
    • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext)
  3. 定义一个或多个BeanDefinitionParser。这个就是真正解析配置的地方
  4. 注册我们的NamespaceHandler到Spring中。在Spring中需要通过两个文件来告知Spring,分别如下:
    • META-INF/spring.handlers中定义类似如下内容:
      • http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
      • key为命名空间的url。value为我们的NamespaceHandler接口实现类。
    • META-INF/spring.schemas中定义类似如下内容:
      • http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
      • key为我们在xml中定义的约束的url,也就是xsd的地址。value为xsd的路径。

翻译完毕。通过上面的翻译,那直接去dubbo的jar包中的META-INF中找吧~

经过上面的分析,我们知道spring.handlers定义了我们的命名空间,以及其对应的NamespaceHandler的实现类,而spring.schemas定义了我们的约束和其对应的地址。那么在Dubbo中,它是这样定义的。

  • spring.handlers
1
2
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
  • spring.schemas
1
2
http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd

为什么会有两个呢?这还是因为历史原因,一个是alibaba dubbo,一个是apache dubbo。那现在回过头去看一下上面xml截图中的红框部分,是不是有点意会到他们是干什么的了?

通过上面两个配置文件,以及上面的铺垫,我们心里已经有一点B树了。我们的<dubbo:xxx>的解析都在org.apache.dubbo.config.spring.schema.DubboNamespaceHandler中定义,下面我们就来看看这个类。

DubboNamespaceHandler

历经前面这么多字的铺垫,终于是见着我们的入口了。但是我们打开这个类发现,里面的代码逻辑很简单,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}

@Override
public void init() {
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());
}
}

先不看里面的具体含义,我们在上面也说了,我们不会直接实现NamespaceHandler,而是会去选择继承他的一个实现类NamespaceHandlerSupport。它里面帮我们做好了一些事情,我们继承它后,可以省很多重复的代码。我们稍微看一眼它的代码。我会省略一些和我们无关的代码,毕竟我们在分析Dubbo,而不是Spring。

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
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
//存放我们的真正做事的解析类
private final Map<String, BeanDefinitionParser> parsers = new HashMap();
//省略部分属性

public NamespaceHandlerSupport() {
}
//解析标签
public BeanDefinition parse(Element element, ParserContext parserContext) {
return this.findParserForElement(element, parserContext).parse(element, parserContext);
}

//寻找解析器,简单点就是通过标签名找解析器
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}

public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
//...省略实现代码
}

private BeanDefinitionDecorator findDecoratorForNode(Node node, ParserContext parserContext) {
//....省略实现代码
}

//保存我们的解析器到Map
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}

protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) {
//...省略实现代码
}

protected final void registerBeanDefinitionDecoratorForAttribute(String attrName, BeanDefinitionDecorator dec) {
//...省略实现代码
}
}

然后再回到DubboNamespaceHandler中。我们发现它只是重写了NamespaceHandlerinit()方法,在里面我们会发现一些很眼熟的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
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());

registerBeanDefinitionParser()方法我们已经说过了,就是把解析保存到Map中。然后key就是参数1,也就是标签名。那registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));举例来说,这里的含义就是,只要是碰到了<dubbo:application>标签,都会进到new DubboBeanDefinitionParser(ApplicationConfig.class, true)中解析~~,到最后真正干活的就是ApplicationConfig~~,具体解析工作是在里面的parse()方法进行。其余的意识都是一样的,我这里就不多说了。

还记得我们在xml中配置Dubbo服务用的什么标签么?没错就是它——<dubbo:service>。我们可以很清晰的在上面看到这一段代码registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));。这也就是我们分析服务注册与发布的真正入口。

没错,这个解析和ServiceBean就放到下篇来说了。每篇专注一个知识点,等一起搞完,串起来,你就自然而然的懂了。

总结

这一篇,篇幅很短,但是确实至关重要的一步。任何源码分析,找对入口是关键。入口对了,里面的逻辑实现,还不是手到擒来?

这一篇我们就简单的说了下Spring的自定义xml解析的机制,通过这个机制我们找到了分析Dubbo的入口点,为我们分析dubbo的服务注册与发布迈出了史诗级的一步。

文章就到这里了。感谢大家的观看。如有不当之处或者疑问,欢迎在下发评论中提出。谢谢~


评论