Dubbo通常使用@Reference來引用服務,通過ReferenceAnnotationBeanPostProcessor類,我們可以掃描被@Reference注解標注的對象,實現服務引用。當引入服務自省后,服務引用過程部分內容與之前的原理有所不同,在本文中著重解釋不同的地方,相同的地方不做具體解釋。
在服務引用時,最終會調用 ReferenceConfig.get() 方法。

然后在ReferenceConfig.init()方法中調用createProxy方法創(chuàng)建代理對象。

在createProxy方法中調用Protocol.refer方法進行服務引用。
因為開啟了服務自省,所以協議為service-discovery-registry

由于Protocol運用了SPI機制,最后一步使用裝飾者模型將創(chuàng)建的對象封裝到對應的Wrapper對象中,
所以,在該處會調用ProtocolFilterWrapper.refer方法

接著調用ProtocolListenerWrapper.refer方法,然后由于開啟了服務自省,所以就
調用ServiceDiscoveryRegistryProtocol.refer方法,ServiceDiscoveryRegistryProtocol是RegistryProtocol的子類。
在該方法中,首先對url進行處理,當開啟了服務自省時,忽略這一步
ServiceDiscoveryRegistryProtocol類
protected URL getRegistryUrl(URL url) {
return "service-discovery-registry".equals(url.getProtocol()) ? url : super.getRegistryUrl(url);
}
RegistryProtocol類
protected URL getRegistryUrl(URL url) {
return URLBuilder.from(url).setProtocol(url.getParameter("registry", "dubbo")).removeParameter("registry").build();
}
列舉一個url樣例:
service-discovery-registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-zookeeper-service-introspection-consumer-sample&dubbo=2.0.2&file=C:\Users\HP/dubbo-cache/dubbo-zookeeper-service-introspection-consumer-sample/dubbo.cache&pid=5304&qos.enable=false&refer=application%3Ddubbo-zookeeper-service-introspection-consumer-sample%26dubbo%3D2.0.2%26init%3Dfalse%26interface%3Dorg.apache.dubbo.spring.boot.sample.consumer.DemoService%26metadata-type%3Dremote%26methods%3DsayHello%26pid%3D5304%26qos.enable%3Dfalse%26register.ip%3D192.168.5.105%26release%3D2.7.8%26revision%3D1.0.0%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1613440867429%26version%3D1.0.0®istry=zookeeper®istry-type=service&release=2.7.8×tamp=1613440867459
然后獲取一個注冊實例,即ServiceDiscoveryRegistry,url換成:
zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-zookeeper-service-introspection-consumer-sample&dubbo=2.0.2&file=C:\Users\HP/dubbo-cache/dubbo-zookeeper-service-introspection-consumer-sample/dubbo.cache&interface=org.apache.dubbo.registry.RegistryService&pid=5304&qos.enable=false®istry-type=service&release=2.7.8×tamp=1613440867459

根據字段獲取Cluster,用于后續(xù)的集群處理,默認是FailoverCluster
接著調用RegistryProtocol.doRefer方法,這是很重要的一步。

在該方法中,首先創(chuàng)建一個RegistryDirectory,賦值注冊實例和協議。
然后訂閱url,url樣例為:
consumer://192.168.5.105/org.apache.dubbo.spring.boot.sample.consumer.DemoService?application=dubbo-zookeeper-service-introspection-consumer-sample&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=org.apache.dubbo.spring.boot.sample.consumer.DemoService&metadata-type=remote&methods=sayHello&pid=3416&qos.enable=false&release=2.7.8&revision=1.0.0&side=consumer&sticky=false×tamp=1613448501648&version=1.0.0
設置此RegistryDirectory為監(jiān)聽器,RegistryDirectory實現了NotifyListener接口。

調用FailbackRegistry.subscribe方法,然后調用ServiceDiscoveryRegistry.doSubscribe方法

writableMetadataService的默認實現類是InMemoryWritableMetadataService,先記錄下訂閱的url
然后獲取服務應用名,主要有三種查找方法,1)通過參數"provider-by"指定,2)訪問配置中心,3)通過參數“subscribed-services”指定
遍歷提供該服務的應用,調用subscribleURLs方法訂閱

從注冊中心根據應用名獲取serviceInstance,serviceInstance記錄了服務端的host和port,和存儲元數據方式等信息。
調用ServiceDiscoveryRegistry.getExportedURLs方法獲取該應用所提供的所有服務。
調用ServiceDiscovryRegistry.expungeStaleRevisionExportedURLs方法。
private final Map<String, Map<String, List<URL>>> serviceRevisionExportedURLsCache = new LinkedHashMap();
從serviceRevisionExportedURLsCache中根據key獲取該應用提供的服務,key為應用名。
如果獲取的內容不為空,執(zhí)行下面的方法。
private void expungeStaleRevisionExportedURLs(List<ServiceInstance> serviceInstances) {
String serviceName = ((ServiceInstance)serviceInstances.get(0)).getServiceName();
Map<String, List<URL>> revisionExportedURLsMap = this.getRevisionExportedURLsMap(serviceName);
if (!revisionExportedURLsMap.isEmpty()) {
Set<String> existedRevisions = revisionExportedURLsMap.keySet();
Set<String> currentRevisions = (Set)serviceInstances.stream().map(ServiceInstanceMetadataUtils::getExportedServicesRevision).collect(Collectors.toSet());
Set<String> staleRevisions = new HashSet(existedRevisions);
staleRevisions.removeAll(currentRevisions);
staleRevisions.forEach(revisionExportedURLsMap::remove);
}
}
然后調用ServiceDiscoveryRegistry.initializeRevisionExportedURLs方法。


隨機選擇一個ServiceInstance,然后‘獲取該應用下提供的所有服務。下述代碼去除一些非重要部分。
先從serviceRevisionExportedURLsCache緩存中獲取,key為應用名,value是一個Map,其中的key為revision,
如果獲取不到,則調用getExportedURLs方法利用MetadataService服務獲取該服務端提供的所有服務。
private List<URL> initializeRevisionExportedURLs(ServiceInstance serviceInstance) {
String serviceName = serviceInstance.getServiceName();
String revision = ServiceInstanceMetadataUtils.getExportedServicesRevision(serviceInstance);
Map<String, List<URL>> revisionExportedURLsMap = this.getRevisionExportedURLsMap(serviceName);
List<URL> revisionExportedURLs = (List)revisionExportedURLsMap.get(revision);
boolean firstGet = false;
if (revisionExportedURLs == null) {
if (!revisionExportedURLsMap.isEmpty()) {
} else {
firstGet = true;
}
revisionExportedURLs = this.getExportedURLs(serviceInstance);
if (revisionExportedURLs != null) {
revisionExportedURLsMap.put(revision, revisionExportedURLs);
}
}
return revisionExportedURLs;
}
}
調用metadataService.getExportedURLs()方法發(fā)起遠程調用。

在樣例中,該服務端提供了兩個服務,所以返回兩個url

調用cloneExportedURLs方法克隆剩余ServiceInstance,只修改了host和port。
設置監(jiān)聽器監(jiān)聽目錄/services/應用名,當該目錄下數據發(fā)生變化時,通過調用subscribeURLs方法 重新建立對遠程服務的引用
調用notifyAllSubscribedURLs方法通知所設置的監(jiān)聽器。
并調用Invoker<T> invoker = cluster.join(directory);方法生成invoker。
之后調轉到ReferenceConfig類中,根據metadata類型,獲取對應的 WritableMetadataService,在本樣例中為RemoteWritableMetadataService,發(fā)布ServiceDefinition
最后將invoker創(chuàng)建為一個代理對象。
并將相關信息存儲在ConsumerModel中

發(fā)布ReferenceConfigInitializedEvent事件。
至此,在消費端的分析就結束了。