先分析XML方式啓動Spring和Dubbo
下載Dubbo源碼後我們主要從dubbo-demo->dubbo-demo-xml模塊中分析源碼
1.先分析dubbo-demo-xml-provider項目
分析之前先修改下log4j的配置文件輸出更多內容方便代碼跟蹤分析
先修改日誌輸出爲Debug
log4j.rootLogger=debug, stdout
在輸出內容上
log4j.appender.stdout.layout.ConversionPattern=[%d{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2} %M %m%n =>
在%c{2} 後面增加%M 增加方法輸出
先看配置文件:dubbo-provider.xml
裏面標籤基本是<dubbo:***> 是dubbo擴展了Spring的schema.這個屬於spring源碼部分不做解釋,看Dubbo源碼前有必要去看下Spring的源碼不然很多地方不會理解,從標籤找到Spring的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 DubboBeanDefinitionParser(AnnotationBean.class, true));
}
這裏就是Dubbo擴展的標籤,通過Spring解析後每個標籤對應的類,比如service標籤解析後會變成ServiceBean類舉個例子吧..
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>這個解析過後差不多和
<bean id="org.apache.dubbo.demo.DemoService" class="com.alibaba.dubbo.config.spring.ServiceBean">
<property name="interface" value="org.apache.dubbo.demo.DemoService"/>
<property name="ref" ref="demoService"/>
</bean> 差不多吧,實際上有很多其他的配置
有興趣可以看下每個配置文件的配置信息.
mian方法中只有兩句代碼很單間:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
context.start();
第一行代碼一般是Spring準備上下文
這裏可以複習下Spring源碼啓動過程
跟蹤ClassPathXmlApplicationContext類的構造方法可以在類中跟蹤到refresh()方法
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//準備刷新的上下文環境,例如對系統屬性或者環境變量進行準備及驗證。
prepareRefresh();
//初始化BeanFactory,並進行XML文件讀取,
//這一步之後,ClassPathXmlApplicationContext實際上就已經包含了BeanFactory所提供的功能,也就是可以進行Bean的提取等基礎操作了。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//對BeanFactory進行各種功能填充,@Qualifier與@Autowired這兩個註解正是在這一步驟中增加的支持。
//設置@Autowired和 @Qualifier註解解析器QualifierAnnotationAutowireCandidateResolver prepareBeanFactory(beanFactory);
try {
//子類覆蓋方法做額外的處理,提供了一個空的函數實現postProcessBeanFactory來方便程序員在業務上做進一步擴展。
postProcessBeanFactory(beanFactory);
//激活各種BeanFactory處理器
invokeBeanFactoryPostProcessors(beanFactory);
//註冊攔截Bean創建的Bean處理器,這裏只是註冊,真正的調用是在getBean時候
registerBeanPostProcessors(beanFactory);
//爲上下文初始化Message源,即不同語言的消息體進行國際化處理
initMessageSource();
//初始化應用消息廣播器,並放入“applicationEventMulticaster”bean中
initApplicationEventMulticaster();
//留給子類來初始化其它的Bean
onRefresh();
//在所有註冊的bean中查找Listener bean,註冊到消息廣播器中
registerListeners();
//初始化剩下的單實例(非惰性的)
finishBeanFactoryInitialization(beanFactory);
//完成刷新過程,通知生命週期處理器lifecycleProcessor刷新過程,同時發出ContextRefreshEvent通知別人
finishRefresh();
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
在這個方法裏看到spring基本的啓動流程包括類的註冊都在這裏完成
在這個方法最後的finishRefresh()方法裏可以看到this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this))); 發出了一個ContextRefreshEvent事件
根據以往經驗,事件都是廣播異步的執行所以直接代碼跟蹤是跟蹤不了的..然後我們只需要在 this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this))); 這句代碼上加個斷點.然後跑程序在到達這個斷點前的控制檯輸出清除然後再繼續跑程序就會看到這個廣播會觸發哪些事件方法.
[06/06/20 11:31:57:920 CST] main DEBUG support.DefaultListableBeanFactory doGetBean: Returning cached instance of singleton bean 'org.apache.dubbo.demo.DemoTwoService'
[06/06/20 11:31:57:930 CST] main INFO config.AbstractConfig onApplicationEvent: [DUBBO] The service ready on spring started. service: org.apache.dubbo.demo.DemoService, dubbo version: , current host: 192.168.220.1
.....
可見執行了 this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this))); 發放後打印了以上幾句..然後我們分析下第一句代碼是spring返回一個DemoService對象,
Returning cached instance of singleton bean 'org.apache.dubbo.demo.DemoService' 在Spring的源碼裏查找是AbstractBeanFactory類中的 doGetBean 方法打印的.看過Spring源碼的都很熟悉這個方法是幹嘛的.不過本文是Dubbo源碼分析就不詳細說明了.
第二句The service ready on spring started....是在AbstractConfig的onApplicaEvent 這個熟悉Spring的一定就能馬上想到ApplicationListener..
好了我們現在找onApplicationEvent這個方法...過程挺蛋疼輸出是 AbstractConfig..但是你去這個類實際上找不到..原因就是程序員偷懶了..在真正輸出的地方logger.info("The service ready on spring started. service: " + getInterface());這個logger對象是從AbstractCofnig繼承下來的..所以.....反正我每次都是獲取本類的class生產的Logger....通過查找The service ready on spring started這句話我們找到這個方法是在ServiceBean中被調用的..查看onApplicationEvent方法可以發現export()方法.跟蹤這個方法的調用,進入export()->doExportUrls()->doExport()->doExportUrls()..中間很多方法都是check檢查參數啥的就略過去了.有空看看也沒啥.
分析doExportUrls方法:
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
logger.debug("pathKey:"+pathKey);
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
ApplicationModel.initProviderModel(pathKey, providerModel);
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
List<URL> registryURLs = loadRegistries(true);
查看loadRegistries方法很容易發現這個是加載Dubbo的註冊中心配置:有意思的是在這發現了一段代碼:for (RegistryConfig config : registries) 便利.可見Dubbo是支持多註冊中心的.具體用法百度下.其實看這段代碼也能看出來.多搞幾個<register >標籤配置上就行了 因爲上面提過<dubbo:register> 解析完就是RegistryConfig類.
繼續看
for (ProtocolConfig protocolConfig : protocols) 會發現Dubbo也支持多協議..主要看doExportUrlsFor1Protocol方法該方法略長
在if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)之前基本都是準備參數.自己看下 導出服務時的參數主要來源於哪些配置:
這段代碼我剛開始看還以爲是擴展類加載器= =,後面自己想了下不太對 這玩意和類加載器根本不沾邊 這東西是Dubbo的擴展加載主類,至於這玩意是啥可以看另外一篇文章 Dubbo的擴展加載機制 如果不看你可以當Java的SPI理解,差不多的東西.
這裏注意下
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
Dubbo會根據協議去配置url,Dubbo是使用URL作爲配置載體的,通過URL可以讓Dubbo的配置在各個模塊傳遞.
再往下面是對scope進行判斷,也不難分析. scope爲none時不導出(export),當不指定remote時是導出到本地,不爲local導出遠程.
看下導出本地方法:
private void exportLocal(URL url) {
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
Exporter<?> exporter = protocol.export(
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
先找下protocol和PROXY_FACTORY 這裏都是用的dubbo默認的,protocol網上很多說是使用的是InjvmProtocol,其實並不是看其初始化代碼ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(),這個ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()這個類是生成一個適配器類,在執行export的時候會動態去加載適合的Protocol, PROXY_FACTORY和protocol是一樣的.不過在執行過程中到處到本地會用InjvmProtocol 導出到遠程會根據Invoker的Url屬性去動態選擇相應的Protocol.可以看做protocol默認的是InjvmProtocol PROXY_FACTORY默認的是JavassistProxyFactory
本地導出沒什麼難的主要幾個步驟
1.形成新的local URL
2.使用代理工廠生產一個Invoker,Invoker網上有很多文章解釋 隨便看看,官方文檔也可以看看.其實就是封裝一個類.其實你看下生成類的源碼就行.在CLassGenerator的toClass方法打印mClassName,mInterfaces,mFields,mMethods,mConstructors的內容就行.然後就非常好理解了. 這個生成的類有invokeMethod看下這個方法.不難沒啥難理解的地方,Dubbo默認的PROXY_FACTORY是JavassistProxyFactory官方文檔上有
3.執行InjvmProtocol的export方法,改方法也簡單就是簡單的創建了一個InjvmExporter對象
4.加入本地導出服務緩存
然後看下導出服務到遠程的代碼,比導出本地要複雜不少,不過也不難
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (!isOnlyInJvm() && logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) { //循環註冊中心
//if protocol is only injvm ,not register 如果該服務指明註冊中心使用injvm,則不進行註冊
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));//Invoker的生產和之前說的沒區別,只不過加了個RUL參數過去
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //這裏是封裝Invoker和當前ServiceConfig , DelegateProviderMetaDataInvoker就是個元組,此類還實現了Invoker接口,適配器模式?
Exporter<?> exporter = protocol.export(wrapperInvoker); //這裏就是我剛纔說的那個情況,在上面代碼 export會執行InjvmProtocol的export方法,在這裏會根據wrapperInvoker的Url中的protocol去選擇合適的加載這裏使用的是registry然後在配置文件裏查找registry對應的Protocol,在dubbo-registry的dubbo-registry-api的resource/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol找到答案registry=org.apache.dubbo.registry.integration.RegistryProtocol dubbo就是默認使用這個類去導出服務到遠程的.接下來重點分析這個類的內容.
exporters.add(exporter);
}
} else {
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* @since 2.7.0
* ServiceData Store
*/
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
metadataReportService.publishProvider(url);
}
}
RegistryProtocol的export方法也不難分析
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
URL registryUrl = getRegistryUrl(originInvoker);//獲取註冊中心URL
URL providerUrl = getProviderUrl(originInvoker); //獲得服務提供者的URL
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
// the same service. Because the subscribed is cached key with the name of the service, it causes the
// subscription information to cover.
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); //導出服務
// url to registry
final Registry registry = getRegistry(originInvoker); //獲得註冊器
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl); //提供者註冊的URL
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);//註冊服務
//to judge if we need to delay publish
boolean register = registeredProviderUrl.getParameter("register", true);
if (register) {
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}
// Deprecated! Subscribe to override rules in 2.6.x or before.
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}
doLocalExport:
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
String key = getCacheKey(originInvoker);
return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
});
}
服務的導出關鍵在protocol.export(invokerDelegate) 這裏的protocol依然是之前的說的,可以根據URL的protocol去執行不同Protocol的export,這個可以看生成這個protocol的代碼得出來的,這裏我是改了個源碼讓這種問題更好發現= =,
private static final String CODE_EXT_NAME_ASSIGNMENT = "String extName = %s;\nSystem.err.println(extName);\n"; 我在生成的判斷 語句加了個err輸出.這樣執行到 protocol.export(invokerDelegate) 的時候會打印出protocol的值,這裏打印了dubbo所以就很容易判斷是執行了具體哪個類的export
這裏使用的是dubbo默認的協議dubbo在源碼裏查詢dubbo= 會 查詢出來唯一的Protocol爲dubbo的dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol,看下這個類的export具體幹了什麼.
主要openServer方法.這個方法本身也沒什麼太多東西,其實主要就是創建服務器,具體是在 Exchangers.bind 方法打開的,其中有這麼句代碼ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type); 這個type值是header找下header的Exchanger執行的是bind方法
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
嵌套的方法都是在封裝對象參數之類 主要是 Transporters.bind方法..這個方法最後調用了getTransporter() 這個也和Protocol一樣是生成了Transporter自適配類.
生成的代碼顯示是獲取的URL的transporter字段查找服務器Transport的,因爲Transporter類的SPI註解指定了netty字符串,說明默認使用nettyTransoport找下netty=查找到的文件裏面是
netty4=org.apache.dubbo.remoting.transport.netty4.NettyTransporter
netty =org.apache.dubbo.remoting.transport.netty4.NettyTransporter
說明dubbo使用的是netty4作爲默認服務器. 查看NettyTransporter類的bind方法,很明顯是創建netty服務器.就不做詳細分析了,玩過netty的也不難看懂這塊的代碼
現在回到RegistryProtocol的export方法中,接下來要到註冊中心去註冊服務了.
final Registry registry = getRegistry(originInvoker);
這個方法是獲取註冊getRegistry代碼也是一樣是一個RegistryFactory的自適配器,這裏我使用的zookeeper註冊中心,所以獲取的是ZookeeperRegistryFactory調用registryFactory.getRegistry產生的是ZookeeperRegistry,跟蹤代碼
if (register) {
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}
這裏是註冊服務的地方,跟蹤進去也不難找到Dubbo如何註冊到zookeeper上的.
看下服務的消費:
看下dubbo的demo
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
context.start();
DemoService demoService = context.getBean("demoService", DemoService.class);
String hello = demoService.sayHello("world");
在服務的使用中,服務和普通的spring bean沒有區別,從容器中直接getBean獲取類調用該方法即可.
配置服務引用的配置如下
<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>
之前在服務提供流程源碼分析過<dubbo:reference >標籤會解析成爲 ReferenceBean 類,看下ReferenceBean的組成extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean
實現了FactoryBean標誌改類爲工廠類,在獲取bean的時候會調用該方法的getObject方法獲取類.跟蹤getObject方法
get()->init()->createProxy()
private T createProxy(Map<String, String> map) {
if (shouldJvmRefer(map)) { // 本地引用
URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
invoker = REF_PROTOCOL.refer(interfaceClass, url); // 調用 refer 方法構建 InjvmInvoker 實例
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else { // 遠程引用
urls.clear(); // reference retry init will add url to urls, lead to OOM
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address. url是ReferenceConfig也就是<dubbo:reference>的url屬性,在文檔找到該屬性解釋就理解這塊了
String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (StringUtils.isEmpty(url.getPath())) {
url = url.setPath(interfaceName);
}
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) { // 檢測 url 協議是否爲 registry,若是,表明用戶想使用指定的註冊中心
// 將 map 轉換爲查詢字符串,並作爲 refer 參數的值添加到 url 中
urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
} else {
// 合併 url,移除服務提供者的一些配置(這些配置來源於用戶配置的 url 屬性),
// 比如線程池相關配置。並保留服務提供者的部分配置,比如版本,group,時間戳等
// 最後將合併後的配置設置爲 url 查詢字符串中
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else { // assemble URL from register center's configuration 裝配註冊中心配置也就是<dubbo:register>
// if protocols not injvm checkRegistry
if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())){ //判斷不是injvm協議
checkRegistry();
List<URL> us = loadRegistries(false);
if (CollectionUtils.isNotEmpty(us)) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
}
}
if (urls.size() == 1) { //單註冊中心
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)); //調用protocol的refer方法.之前講過是使用url的protocol來判斷使用哪個protocol,這裏是registry
} else { //多註冊中心
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
//下面使用Cluster合併多個註冊中心
if (registryURL != null) { // registry url is available
// use RegistryAwareCluster only when register's CLUSTER is available
URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
// The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else { // not a registry url, must be direct invoke.
invoker = CLUSTER.join(new StaticDirectory(invokers));
}
}
}
if (shouldCheck() && !invoker.isAvailable()) {
throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
/**
* @since 2.7.0
* ServiceData Store
*/
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
metadataReportService.publishConsumer(consumerURL);
}
// create service proxy
return (T) PROXY_FACTORY.getProxy(invoker);
}
主要研究下如何導入服務,這裏就不去研究多註冊中心了我們直接看單註冊中心如何導入服務.這裏直接使用REF_PROTOCOL.refer獲取一個Invoker,查看如何創建的Invoker,根據之前的分析 這裏的REF_PROTOCOL.refer是調用的RegistryProtocol的refer方法方法如下
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = URLBuilder.from(url)
.setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
.removeParameter(REGISTRY_KEY)
.build();
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
String group = qs.get(GROUP_KEY);
if (group != null && group.length() > 0) {
if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
return doRefer(cluster, registry, type, url);
}
在refer方法裏第一件事就獲取註冊中心,我們配置的是zookeeper所以調用了ZookeeperRegistryFactory的getRegistry方法獲取了ZookeeperRegistry.在ZookeeperRegistry構造裏連接了zookeeper..
回頭看下RegistryProtocol的refer方法看源碼看的多的一眼就能看到doRefer一般來說真正做事的都是doXX方法..
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
//創建RegistryDirectory
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
registry.register(directory.getRegisteredConsumerUrl());
}
directory.buildRouterChain(subscribeUrl);
directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
如上,doRefer 方法創建一個 RegistryDirectory 實例,然後生成服務者消費者鏈接,並向註冊中心進行註冊。註冊完畢後,緊接着訂閱 providers、configurators、routers 等節點下的數據。完成訂閱後,RegistryDirectory 會收到這幾個節點下的子節點信息。由於一個服務可能部署在多臺服務器上,這樣就會在 providers 產生多個節點,這個時候就需要 Cluster 將多個服務節點合併爲一個,並生成一個 Invoker。
有了Invoker就可以調用PROXY_FACTORY.getProxy(invoker);生成對象的代理了.
PROXY_FACTORY和之前一樣實際調用的是JavassistProxyFactory的getProxy..別找錯了 JavassistProxyFactory裏面的getProxy有倆參數,真正調用的是父類的方法.在父類的調用路徑中會最終會調用JavassistProxyFactory的getProxy生成代理類在該方法中調用Proxy的getProxy
獲取代理的方法調用棧: ReferenceBean.getObject->ReferenceConfig.get->ReferenceConfig.init->ReferenceConfig.createProxy->JavassistProxyFactory.getProxy方法
DubboProtocol是實現了Dubbo通信協議的
具體調用在DubboInvoker的doInvoke方法裏調用,
需要實現
Invoker 繼承AbstractInvoker
DubboProtocol 繼承AbstractProtocol
RegistryDirectory的NotifyListener實現了notify方法