Dubbo源码分析(一) - 标签解析

本文基于 dubbo 2.7.8 版本

前言

spring 在初始化容器的过程中,以 ClassPathXmlApplicationContext 为例,会加载传入的 xml 文件,解析并实例化 xml 中定义的 bean。
dubbo 标签的解析过程,本质上是 dubbo bean 的实例化过程,自然也是发生在 spring 实例化 bean 的过程之中。

spring 自定义标签的解析

解析过程

入口函数,BeanDefinitionParserDelegate文件中的parseCustomElement函数:

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 获取命名空间
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 获取 handler,在获取过程中会调用 handler 的 init 方法
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 调用 handler 的 parse 方法
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

我们需要注意到,这里的parseCustomElement是处于 for 循环中的,入参是一个Element类型的节点,即每一次会解析一个 dubbo 标签。
在上述代码中,spring 首先获取 element 对应的 namespace(这块涉及前置基础 :xml 文档的结构以及 xmlns 的概念),然后通过 namespace 去找到对应的 handler。
我们看 DefaultNamespaceHandlerResolver 的 resolve 函数:

public NamespaceHandler resolve(String namespaceUri) {
// 获取 handlerMappings
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
// 已经解析过则直接返回,避免重复调用 handler 的 init 方法
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 调用 init 方法
namespaceHandler.init();
// 避免下一次重复调用
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}

函数很长,但逻辑很简单,这块代码涉及到 spring 中 namespace 对应 handler 的加载机制。在 dubbo 源码的 META-INF/spring.handlers 中定义 dubbo 对应的 namespace 应该用哪个 handler 来处理:

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

这是一个字符串,因此在上述代码第四行获取的 handlerOrClassName 一开始会是一个字符串,值为
org.apache.dubbo.config.spring.schema.DubboNamespaceHandler。
spring 通过反射的方法找到这个 handler 并实例化,然后调用 init 方法,并缓存这个 handler,避免下一次重复加载。
我们看下 DubboNamespaceHandler.init 方法:

@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("ssl", new DubboBeanDefinitionParser(SslConfig.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());
}

注册了很多所谓的 BeanDefinitionParser,但我们仔细一看,发现注册的竟然都是 DubboBeanDefinitionParser 这个解析器。我们知道 BeanDefinition 就是一个描述 Bean 的 class,因此这里就是给不同的标签用同一个解析器注册(放到一个集合中)了一下,标注下每个标签要转换成的对应的 bean 的类。在后面要调用 parse 方法的时候,根据标签的名字组装对应的 bean 就行了。

扩展:dubbo 中各类型标签详解

我们回到 spring 中。
spring 在获取到 handler 之后直接调用了 handler 的 parse 方法,传入 element 和表示解析上下文的 context。

// DubboNamespaceHandler:parse(Element element, ParserContext parserContext)
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
registerAnnotationConfigProcessors(registry);
/**
* @since 2.7.8
* issue : https://github.com/apache/dubbo/issues/6275
*/
registerCommonBeans(registry);
BeanDefinition beanDefinition = super.parse(element, parserContext);
setSource(beanDefinition);
return beanDefinition;
}


// NamespaceHandlerSupport:parse(Element element, ParserContext parserContext)
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}

// DubboBeanDefinitionParser:parse(Element element, ParserContext parserContext)
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass, required);
}

然后就调用到了之前注册的 DubboBeanDefinitionParser 解析器中的 parse 方法中,这里在之前注册的时候指定了每一个字符串类型 element 对应的 beanClass 以及 required 属性(required 与 spring 中的 @Required 含义相同,为了保证 bean 一定要被加载)。
然后就到了最终的 parse 方法了,这个方法太长了,我就不一行一行写注释了(可以写但 maybe 要)。
主要说一下主流程:

  1. 这个 parse 方法的目的就是将一个 element 转成一个 bean,放到 spring 容器中。在源码中是用 RootBeanDefinition 这个类来表示最终的 bean 的。
  2. 创建一个 RootBeanDefinition 对象,设置 beanClass 以及 lazy-init = false。
  3. 为了注册到 spring 容器中需要获取 bean 的 name,如果重复则通过 id 递增。
  4. 通过 if 语句分别对不同的标签类型做处理,主要目的就是创建 bean 以及设置一些 property。

还要补充一下,有些处理分支会创建 BeanDefinitionHolder 和 RuntimeBeanReference,那么这两个 bean 又有什么作用呢?

  • BeanDefinitionHolder : 这个holder bean 本身是代表着一个持有 name 和 alias 的 bean,在依赖注册时会根据 name 来注册 holder 中 bean。比如 ServiceBean 中的处理,service 标签是通过 class 属性指定对应的实现类的,因此这里是给 service 这个 bean 添加一个 ref 指向 serviceIdImpl 的实现类的 bean。
  • RuntimeBeanReference:运行时 bean 引用,构造的时候还是没有实例化,在运行时才会被解析。
文章作者: yPhantom
文章链接: https://guoyuxiang.cn/2021/04/25/dubbo-source1/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Life Note