前言
上一节, 我们通过与 Spring 集成的实例, 把 Dubbo 项目跑了起来. 但是 Dubbo 项目是怎么运行起来的呢? 它的入口在哪里?
在官网上有这么一句话: Dubbo 采用全 Spring 配置方式, 透明化接入应用, 对应用没有任何 API 侵入, 只需用 Spring 加载 Dubbo 的配置即可, Dubbo 基于 Spring 的 Schema 扩展进行加载.
一, Spring 的初始化
1, 解析配置文件
不知诸位是否还有印象, Spring 初始化的流程是什么样的呢? 如果有不了解的同学, 那你可以先看看笔者的往期文章: Spring 源码分析 (一)Spring 的初始化和 xml 解析 https://www.jianshu.com/p/baa1d48e7f57
加载配置文件
封装成 Resource 对象, 然后解析为 Document 对象
根据 Document 节点信息, 封装 Bean 对象, 并注册
其中, 封装 Bean 信息, 就是调用 parseBeanDefinitions 处理配置文件中的节点信息.
- public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
- protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
- 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)) {
- parseDefaultElement(ele, delegate);
- }
- else {
- delegate.parseCustomElement(ele);
- }
- }
- }
- }
- else {
- delegate.parseCustomElement(root);
- }
- }
- }
2, 查找命名空间
上面的方法里面, 大部分时候会走到 parseCustomElement(ele); 它通过获取配置文件中的 namespaceUri, 来获得 NamespaceHandler, 然后调用其 parse 方法, 完成对 Bean 的注册.
- public class BeanDefinitionParserDelegate {
- public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
- // 在 Dubbo 中, namespaceUri 就是 http://dubbo.apache.org/schema/dubbo
- String namespaceUri = getNamespaceURI(ele);
- // 根据命名空间获取处理类
- NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
- if (handler == null) {
- error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
- return null;
- }
- // 调用方法, 完成对 Bean 的加载和注册
- return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
- }
- }
那么, 问题的关键就变成了: 如何来一个确定 NamespaceHandler?
在 Spring 中, 它会通过 AppClassLoader 加载所有的 META-INF/spring.handlers 文件, 转换成 Map<String, Object> handlerMappings, 那么在 Dubbo 源代码中, 就有一个这样的文件. 内容为:
- http://dubbo.apache.org/schema/dubbo=
- com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
- http://code.alibabatech.com/schema/dubbo=
- com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
很显然, namespaceUri 为 http://dubbo.apache.org/schema/dubbo 就对应 com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler 处理类.
然后 Spring 会通过反射, 实例化 DubboNamespaceHandler 对象, 调用其初始化方法 init()
- NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
- namespaceHandler.init();
- handlerMappings.put(namespaceUri, namespaceHandler);
初始化之后, 返回 NamespaceHandler 对象, 调用 parse() 来处理 Bean.
3,DubboNamespaceHandler
在 DubboNamespaceHandler 的初始化方法中, Dubbo 为每个节点注册了相同的处理类 DubboBeanDefinitionParser, 但需要注意, 它们的 beanClass 不同.
- public class DubboNamespaceHandler extends NamespaceHandlerSupport {
- 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("monitor", new DubboBeanDefinitionParser(MonitorConfig.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());
- }
- }
所以, 在调用 handler.parse() 方法时, 实际调用的是父类的方法. 父类方法中, 通过配置文件节点的名称, 找到对应的处理类, 实际调用 parse. 那么在 Dubbo 中, 大部分调用到的就是 DubboBeanDefinitionParser.parse()
- public abstract class NamespaceHandlerSupport implements NamespaceHandler {
- public BeanDefinition parse(Element element, ParserContext parserContext) {
- return findParserForElement(element, parserContext).parse(element, parserContext);
- }
- private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
- // 获取配置文件中的节点名称
- String localName = parserContext.getDelegate().getLocalName(element);
- // 找到对应的处理类返回
- BeanDefinitionParser parser = this.parsers.get(localName);
- return parser;
- }
- }
看完这个过程, 我们再回到开头引用的官网上那句话, 就已经明白了. Dubbo 就是通过这种方式进行加载的.
二, 加载 Bean
上面我们看到 Spring 已经扫描到 Dubbo 的配置文件, 接下来就是解析并构建 BeanDefinition.
这块代码比较长, 因为它是把所有的节点都放在一个类来处理的, 依靠 beanClass 来确定当前处理的是哪个节点, 所以里面充斥着大量的 IE else 判断.
它的作用就是, 把配置文件中的每一个标签, 封装成 BeanDefinition 对象, 然后处理这个对象的属性和方法, 最后注册到容器中, 等待 Spring 在实例化的时候遍历它们.
我们以上一章节生产者端的配置文件为例, 它们解析之后, 都会封装为 BeanDefinition 对象, 放入 beanFactory.
- dubbo_producer1=Root bean: class [com.alibaba.dubbo.config.ApplicationConfig]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null,
- dubbo=Root bean: class [com.alibaba.dubbo.config.ProtocolConfig]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null,
- com.alibaba.dubbo.config.RegistryConfig=Root bean: class [com.alibaba.dubbo.config.RegistryConfig]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null,
- com.viewscenes.netsupervisor.service.InfoUserService=Root bean: class [com.alibaba.dubbo.config.spring.ServiceBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null,
- infoUserService=Generic bean: class [com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [dubbo_provider1.xml]
三, Bean 的实例化
上一步从配置文件中读取信息, 封装成 BeanDefinition 对象之后, Spring 要循环这些 Bean, 对它们进行实例化和依赖注入. 关于这一块知识的具体内容, 请参考笔者往期文章: Spring 源码分析 (二)bean 的实例化和 IoC 依赖注入 https://www.jianshu.com/p/00f1a6739a9e
回到 Dubbo 上来说, 配置文件中的每一个节点都对应一个处理类.
- dubbo:application> ApplicationConfig.class
- dubbo:registry> RegistryConfig.class
- dubbo:protocol> ProtocolConfig.class
- dubbo:service> ServiceBean.class
显然, 在 Spring 进行实例化和依赖注入的时候, 势必会调用到这些类的方法. 而在这些业务方法里, Dubbo 就激活了整个框架的各个部件.
我们以 dubbo:service 为例, 它对应的是 ServiceBean.class, 它实现了 Spring 中的不同接口, 就是在 Spring Bean 的不同时期调用方法, 最后完成 Dubbo 的服务暴露.
- package com.alibaba.dubbo.config.spring;
- public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean,
- DisposableBean, ApplicationContextAware,
- ApplicationListener<ContextRefreshedEvent>, BeanNameAware {
- public void setApplicationContext(ApplicationContext applicationContext) {
- this.applicationContext = applicationContext;
- //......
- }
- public void onApplicationEvent(ContextRefreshedEvent event) {
- // 服务暴露
- export();
- }
- public void afterPropertiesSet() throws Exception {
- // 类初始化方法
- //......
- }
- }
那么, 其他的配置节点也都是一样, 就是 Spring 在实例化 Bean 的时候调用到 Dubbo 里的代码, 完成它们各自的使命.
来源: https://juejin.im/post/5c909835e51d450fd77f9c89