Dubbo原始碼分析(二)Dubbo是從哪裡初始化的?

清幽之地發表於2019-03-19

前言

上一節,我們通過與Spring整合的例項,把Dubbo專案跑了起來。但是Dubbo專案是怎麼執行起來的呢?它的入口在哪裡?

在官網上有這麼一句話:Dubbo 採用全 Spring 配置方式,透明化接入應用,對應用沒有任何 API 侵入,只需用 Spring 載入 Dubbo 的配置即可,Dubbo基於Spring的Schema擴充套件進行載入。

一、Spring的初始化

1、解析配置檔案

不知諸位是否還有印象,Spring初始化的流程是什麼樣的呢?如果有不瞭解的同學,那你可以先看看筆者的往期文章:Spring原始碼分析(一)Spring的初始化和XML解析

  • 載入配置檔案
  • 封裝成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依賴注入

回到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裡的程式碼,完成它們各自的使命。

相關文章