Dubbo的分支: 3.0
Dubbo的服務提供者會將RPC服務的調用說明,導出到配置中心。然后服務的消費者向配置中心訂閱這些服務,也就是引用這些服務。
服務端-服務提供方-暴露/導出服務
-
dubbo根據spring的擴展api,增加了dubbo命名空間,以及各種xml元素,用來配置服務。
- 關于dubbo和spring的集成,以及spring對自定義的命名空間解析,詳見NamespaceHandler,NamespaceHandlerSupport的使用。
spring boot啟動,解析dubbo框架xml配置文件,裝載實例,放入spring容器,到這相當于實例化bean完成。
-
dubbo增加了應用上下文監聽類DubboBootstrapApplicationListener,類內覆蓋onApplicationContextEvent(ApplicationContextEvent event)方法,啟動dubbo。
@Override public void onApplicationContextEvent(ApplicationContextEvent event) { //使用事件監聽器的方式與spring集成 if (event instanceof ContextRefreshedEvent) { onContextRefreshedEvent((ContextRefreshedEvent) event); } else if (event instanceof ContextClosedEvent) { onContextClosedEvent((ContextClosedEvent) event); } } private void onContextRefreshedEvent(ContextRefreshedEvent event) { //啟動dubbo dubboBootstrap.start(); }
-
org.apache.dubbo.config.bootstrap.DubboBootstrap#start
-
如果是第一次啟動的話,調用initialize()完成spi加載、配置中心初始化、事件監聽等初始化。
public DubboBootstrap start() { if (started.compareAndSet(false, true)) { startup.set(false); initialize(); //start方法內有兩行關鍵的代碼,一個是服務提供方開始暴露/導出所有服務,一個是消費方開始發現/引用服務 // 1. 導出dubbo所有服務 exportServices(); // Not only provider register if (!isOnlyRegisterProvider() || hasExportedServices()) { // 2. export MetadataService exportMetadataService(); //3. Register the local ServiceInstance if required registerServiceInstance(); } //消費者引用服務 referServices(); //省略... } return this; }
-
調用exportServices暴露/導出服務。
private void exportServices() { //從配置中心拿到配置的所有service bean,依次導出。 configManager.getServices().forEach(sc -> { ServiceConfig serviceConfig = (ServiceConfig) sc; serviceConfig.setBootstrap(this); if (exportAsync) { //異步導出 ExecutorService executor = executorRepository.getServiceExporterExecutor(); Future<?> future = executor.submit(() -> { sc.export(); exportedServices.add(sc); }); asyncExportingFutures.add(future); } else { //同步導出 sc.export(); exportedServices.add(sc); } }); }
-
遍歷所有服務,依次調用org.apache.dubbo.config.ServiceConfig#export,導出服務。最終調用org.apache.dubbo.config.ServiceConfig#doExportUrls,對該服務配置的暴露協議,每個協議暴露一份服務。
private void doExportUrls() { //repository.services存有所有的服務描述對象,包括默認加載的隱含服務, //如EchoService,GenericService,MonitorService,MetricsService //repository.providers存有所有的服務提供者對象 //repository.consumers存有所有的服務消費者對象 ServiceRepository repository = ApplicationModel.getServiceRepository(); //此處省略部分代碼...... //獲取向配置中心注冊的服務url格式,即registry://xxxx List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true); for (ProtocolConfig protocolConfig : protocols) { //此處省略部分代碼...... //因為一個服務可以使用多種通訊協議,所以此處是每個協議導出一份服務配置,如dubbo/http/injvm協議各一份 doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }
-
導出的核心方法:org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol
- 因為dubbo設計的是使用url作為配置總線,代表最終的導出格式。這個方法會先補充url信息,如失敗重試retry、token、Generic服務等數據。
- 判斷一個服務只要不是配置了只能暴露給遠程的話,就會導出一份本地injvm服務,即調用exportLocal(url)。
- 暴露給遠程。
-
單獨說下導出一個服務的主要邏輯
-
這里需要先解釋下dubbo設計的幾個類:Protocol/Exporter/Invoker,說下這三個類的關系。
- Protocol是協議的意思,比如Dubbo、Injvm協議。一個服務要以某個協議的方式暴露出來,即可以用這個協議來訪問這個服務,如HelloService以http協議的方式暴露出來,也就是以后可以用http的方式訪問這個服務。
- Dubbo抽象出來一個Exporter的概念,負責如何暴露服務,如何取消服務暴露。 一個服務被暴露出來,需要知道它該怎么調用,就有了Invoker。Invoker負責實際的調用服務返回響應。
- 綜上,1個Protocol包含多個服務的Exporter,1個Exporter在大部分情況下只包含1個Invoker。
- 從代碼來看三者的關系:
public abstract class AbstractProtocol implements Protocol { //該協議的所有導出者,即暴露的所有服務 //"greeting/org.apache.dubbo.demo.GreetingService:1.0.0:20880" -> DubboExporter對象 //"greeting/org.apache.dubbo.demo.GreetingService:1.0.0" -> InjvmExporter對象 protected final Map<String, Exporter<?>> exporterMap = new ConcurrentHashMap<String, Exporter<?>>(); //省略...... } public abstract class AbstractExporter<T> implements Exporter<T> { //每個AbstractExporter子類都會至少包含一個Invoker private final Invoker<T> invoker; //省略...... }
-
再說下這三者的組裝過程:
public class ServiceConfig<T> extends ServiceConfigBase<T> { //自適應Protocol,默認以DubboProtocol為基礎,按優先級在外層包裝各種包裝類,如:ProtocolFilterWrapper,ProtocolListenerWrapper,QosProtocolWrapper private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); //Invoker動態代理工廠,默認為JavassistProxyFactory private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); //所有導出的服務 private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>(); private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { //省略... if (CollectionUtils.isNotEmpty(registryURLs)) { for (URL registryURL : registryURLs) { //省略... //使用JavassistProxyFactory動態生成、加載invoker字節碼。以GreetingService舉例 //ref=greetingServiceImpl,interfaceClass=GreetingService.class,第三個參數是url=injvm://127.0.0.1/org.apache.dubbo.demo.GreetingService ?anyhost=true&application=demo-provider&bind.ip=192.168.2.3&bind.port=20880 &deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata &mapping.type=metadata&metadata-type=remote&methods=hello&pid=25062&qos.port=22222 &release=&revision=1.0.0&side=provider&timeout=5000×tamp=1615949370199&version=1.0.0 //JavassistProxyFactory.getInvoker方法內部會先生成invoker wrapper包裝類,目的是統一bean的方法調用。生成的wrapper類詳見下面Wrapper1。 Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //使用JavassistProxyFactory動態生成、加載invoker字節碼。以GreetingService舉例 //ref=greetingServiceImpl,interfaceClass=GreetingService.class, //第三個參數是url=injvm://127.0.0.1/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider // &bind.ip=192.168.2.3&bind.port=20880 &deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting // &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata &mapping.type=metadata // &metadata-type=remote&methods=hello&pid=25062&qos.port=22222 &release=&revision=1.0.0&side=provider // &timeout=5000×tamp=1615949370199&version=1.0.0 //JavassistProxyFactory.getInvoker方法內部會先生成invoker wrapper包裝類。生成的wrapper類詳見下面Wrapper1。 Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); //將invoker以某個協議暴露出去,返回exporter //在protocol的多層包裝對象中,有一個ProtocolFilterWrapper,調用export方法時,它會先判斷是否為registry協議,如果不是, //需要先構造invoker的過濾器鏈,增強invoker功能,如MonitorFilter,TimeoutFilter,TraceFilter,ExceptionFilter等,再繼續導出。 Exporter<?> exporter = PROTOCOL.export(wrapperInvoker); 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); } } }
-
服務如何暴露給遠程,即向公共配置中心注冊服務
上面說過我們在xml中配置一個服務,可以指定它以哪些協議的方式暴露出來,比如dubbo,injvm,http,grpc,hessian等
dubbo會遍歷所有協議,每一個協議都會調用doExportUrlsFor1Protocol()來導出對應格式的服務。
dubbo會判斷如果這個協議配置了暴露給遠程,那么會根據當前協議的url,創建一個用于注冊registry的url。如,當前是http協議,框架判斷需要暴露給遠程,即需要注冊到公共配置中心,那么會根據http://xxx,生成一個registry://xxx的url,用于注冊服務。
-
服務導出是由協議對象調用export()觸發,Exporter<?> exporter = PROTOCOL.export(wrapperInvoker)。其中registry://協議是由RegistryProtocol.export負責導出。
@Override public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { //orginInvoker的url=registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?xxx //得到registryUrl=zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?xxx URL registryUrl = getRegistryUrl(originInvoker); //得到providerUrl=dubbo://192.168.2.3:20880/org.apache.dubbo.demo.GreetingService?xxx URL providerUrl = getProviderUrl(originInvoker); //省略... // decide if we need to delay publish boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { //立即向配置中心如zk,注冊服務 register(registryUrl, registeredProviderUrl); } //省略... //Ensure that a new exporter instance is returned every time export return new DestroyableExporter<>(exporter); } private void register(URL registryUrl, URL registeredProviderUrl) { //根據url協議頭,獲取配置中心注冊器對象,得到ZookeeperRegistry Registry registry = registryFactory.getRegistry(registryUrl); //調用ZookeeperRegistry的注冊方法,內部為使用CuratorZookeeperClient向配置中心寫服務數據 //到這里也就完成了將服務注冊到公共配置中心,暴露給遠程了 registry.register(registeredProviderUrl); }
到此服務暴露完畢。
客戶端-服務消費方-發現/引用服務
先需要理解消費方的幾個重要概念
- 先舉個例子,借著例子來理解。假設在北京和上海分別有一個注冊中心zk-beijing,zk-shanghai。在北京有三臺機器,ip分別為1.1.1.1/2.2.2.2/3.3.3.3。其中1.1.1.1為消費者,2.2.2.2和3.3.3.3為服務提供者。2.2.2.2提供ServiceA,ServiceB兩個服務,3.3.3.3提供ServiceA,ServiceC兩個服務。
-
我們先簡化環境,看下只有一個注冊中心的情況,即只向zk-beijing注冊。在2.2.2.2/3.3.3.3啟動初始化時,會向注冊中心注冊暴露服務,路徑為:
//zookeeper路徑結構 /dubbo /ServiceA /providers /URL.encode(dubbo://2.2.2.2:20880/ServiceA?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceA&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=37004&release=&side=provider&timeout=3000×tamp=1617773391401) /URL.encode(dubbo://3.3.3.3:20880/ServiceA?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceA&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=35111&release=&side=provider&timeout=3000×tamp=1617773391507) /ServiceB /providers /URL.encode(rest://2.2.2.2:80/ServiceB?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceB&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=buy,sell&pid=37004&release=&revision=1.0.0&side=provider&timeout=5000×tamp=1617773396526&version=1.0.0) /ServiceC /providers /URL.encode(dubbo://3.3.3.3:20880/ServiceC?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceC&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=go,back&pid=351111&release=&side=provider&timeout=3000×tamp=1617773391507)
從路徑可以看出,dubbo不是以一個機器為基本單位,暴露該機器下有哪些服務的,如:/dubbo/2.2.2.2/我的服務xxx。dubbo是以服務為單位來暴露。因為我們引用都是引用哪個服務,暴露也應該是暴露哪個服務,服務下標明哪些機器上有該服務。你需要引用哪些服務就可以隨意的引用哪些,不需要引用整個機器暴露出來的所有服務。假如一個服務暴露在多臺機器上,就更是個問題了。
再看多注冊中心,假如有另外幾臺機器,向shanghai注冊,情況和beijing差不多。這種多注冊中心存在時,可以看做有兩個異地服務集群,比如HelloService有北京和上海兩個服務集群。
-
概念一:Invoker
- Invoker是什么:從zk樹上看,/providers下的每一個服務url在消費方都會對應一個Invoker。如果1.1.1.1引用了這三個服務,那就會創建4個對應的Invoker。一個Invoker表示一個消費方持有的,對一個提供方某一個服務的引用。Invoker內包含連接池屬性字段ExchangeClient[] clients。因為ClusterInvoker的存在,為了區分,Invoker也叫做普通Invoker。
概念二:Directory
- Directory就是多個Invoker的集合,實現類一般會有一個List<Invoker<T>>屬性字段,存儲Invoker。為了方便表示以及操作多Invoker,抽象出來的概念。
概念三:Router
- Router路由。在實際使用中,可能由于我們部署的物理機性能有差別,有幾臺機器性能特別好,或者說為了測試某幾臺機器上的服務,要求請求更多的或者全部路由到某幾臺機器,Router負責的就是這個路由邏輯。Router會從Directory中遍歷選出與路由規則匹配的invoker子集,然后交給負載均衡器。
概念四:LoadBalance
- LoadBalance(負載均衡器)接收Router篩選出來的可以使用的invoker集合,從中選出一個普通invoker,用于發起實際調用。
概念五:ClusterInvoker
- ClusterInvoker就是實際的某一個物理集群了,如HelloService服務的北京集群。
概念六:Cluster
- Cluster是對實際物理集群抽象出的最上層概念,即某一個服務的所有物理集群的集合叫做Cluster。如HelloService的北京和上海兩個異地物理集群合起來叫做Cluster。
總結
- HelloService服務部署在兩個異地集群,一個北京,一個上海。兩地各自都有一個注冊中心zk。
- 自下而上的組裝過程:
- dubbo會從北京的注冊中心zk讀取HelloService的所有提供者,一個提供者封裝成一個普通Invoker。
- 將多個普通Invoker存儲到一個Directory中。
- 根據業務特性,創建一個對應的集群執行器對象ClusterInvoker。選擇適合的Router和LoadBalance,將Directory傳入。到此北京集群的invoker封裝好了。
- 重復上述步驟,將上海集群也封裝成一個ClusterInvoker。
- 將北京、上海ClusterInvoker封裝到多注冊中心Cluster對象中。
- 服務以Cluster為入口,對外提供服務。
- 自上而下的調用過程:
Cluster會根據訂閱的各個注冊中心的配置、與服務消費方是否處于同一個物理集群、權重等先選出來一個調用的ClusterInvoker,即先選出來一個物理集群。假如選出來的是北京集群。
然后在北京集群ClusterInvoker執行Router路由,選出匹配的普通Invoker集合。
在路由后的Invoker集合上,執行負載均衡,由LoadBalance選出一個最終用來執行RPC的普通Invoker。
-
執行Invoker.invoke(Invocation invocation),發起調用。
//從代碼看下調用過程 public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> { public Result invoke(final Invocation invocation) throws RpcException { //省略 //使用路由器Router從ClusterInvoker.directory存儲的所有的invoker中,路由出符合路由規則的一批invoker。 List<Invoker<T>> invokers = list(invocation); //上面選出了方向正確的一批invoker,現在需要負載均衡選一個執行了。此處初始化負載均衡器LoadBalance。 LoadBalance loadbalance = initLoadBalance(invokers, invocation); //為異步執行附著調用id RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); //使用負載均衡器選一個invoker,發起調用,返回響應。 return doInvoke(invocation, invokers, loadbalance); } }
消費方發現/引用服務的過程
- Dubbo是嚴重依賴Spring框架的,我們在xml配置了java interface > HelloService的Bean聲明和引用關系后,如何在服務消費方生成、加載該接口的實現類,創建實例對象,納入到Spring容器管理,在需要的地方注入。
- Dubbo是由FactoryBean入手的,過程與MyBatis的Mapper類生成過程類似。(注:3.0分支中,只有延遲加載的代理對象是使用FactoryBean功能生成的,普通代理對象是使用ReferenceConfig.get()生成的。而在master分支中,所有代理對象都是使用FactoryBean生成,底層實現也是調用的ReferenceConfig.get())
- 下面說的是3.0分支普通代理對象的生成過程,沒有使用FactoryBean。
-
與服務暴露一樣,服務引用也是使用事件監聽的方式與spring集成,都會調用的org.apache.dubbo.config.bootstrap.DubboBootstrap#start方法,該方法上面說過,有兩個關鍵的點,一個是exportServices():提供方開始暴露/導出所有服務,另一個是referServices():消費方開始發現/引用服務。
public DubboBootstrap start() { if (started.compareAndSet(false, true)) { startup.set(false); initialize(); //start方法內有兩行關鍵的代碼,一個是服務提供方開始暴露/導出所有服務,一個是消費方開始發現/引用服務 // 1. 導出dubbo所有服務 exportServices(); // Not only provider register if (!isOnlyRegisterProvider() || hasExportedServices()) { // 2. export MetadataService exportMetadataService(); //3. Register the local ServiceInstance if required registerServiceInstance(); } //消費者引用服務 referServices(); //省略... } return this; }
-
referServices()中會觸發緩存數據的初始化、加載。
private void referServices() { //configManager.getReferences()拿到的是對xml中配置的所有<dubbo:reference xxx/> //解析后封裝的ReferenceConfig對象 configManager.getReferences().forEach(rc -> { //省略... //cache為ReferenceConfigCache類型,底層是一個ConcurrentHashMap, //調用get會初始化對應的ReferenceConfig對象,放入緩存中 cache.get(rc); }); }
-
ReferenceConfigCache.get(rc),從緩存拿消費方引用的服務代理對象,如果沒有觸發實現類的生成、加載,然后創建代理類放入cache。
public <T> T get(ReferenceConfigBase<T> referenceConfig) { //key=org.apache.dubbo.demo.DemoService String key = generator.generateKey(referenceConfig); //type=DemoService.class Class<?> type = referenceConfig.getInterfaceClass(); //proxies結構=< DemoService.class, <"org.apache.dubbo.demo.DemoService", DemoService實現類代理對象> > proxies.computeIfAbsent(type, _t -> new ConcurrentHashMap<>()); //proxiesOfType結構=<"org.apache.dubbo.demo.DemoService", DemoService實現類代理對象> ConcurrentMap<String, Object> proxiesOfType = proxies.get(type); proxiesOfType.computeIfAbsent(key, _k -> { //由ReferenceConfig配置對象觸發對應服務接口實現類的創建、加載,以及代理對象的創建。 //proxy就是在消費方引用的服務代理對象 Object proxy = referenceConfig.get(); referredReferences.put(key, referenceConfig); return proxy; }); return (T) proxiesOfType.get(key); }
-
關鍵的來了,ReferenceConfig類包含了生成、加載實現類,創建代理對象的主要邏輯。
public class ReferenceConfig<T> extends ReferenceConfigBase<T> { //缺省FailoverCluster private static final Cluster CLUSTER = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension(); //缺省JavassistProxyFactory private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); //根據url協議類型決定,url=registry://xxx,對應RegistryProtocol。url=dubbo://xxx,對應DubboProtocol。url=injvm://xxx,對應InjvmProtocol。 private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); //ClusterInvoker類型 private transient volatile Invoker<?> invoker; //直連提供者地址或者注冊中心地址 protected final List<URL> urls = new ArrayList<URL>(); //引用的代理對象 private transient volatile T ref; //1.生成的代理對象會set到屬性字段中,為null的話,開始引用初始化。 public synchronized T get() { if (ref == null) { //觸發引用初始化 init(); } return ref; } //2. 初始化 public synchronized void init() { if (initialized) return; //解析各種消費方配置,放入map //省略... //map={"mapping-type":"metadata","init":"false","side":"consumer","register.ip":"192.168.2.3", // "release":"","methods":"sayHello,sayHelloAsync","qos.port":"33333","provided-by":"demo-provider", // "dubbo":"2.0.2","pid":"35116","check":"true","interface":"org.apache.dubbo.demo.DemoService", // "enable.auto.migration":"true","mapping.type":"metadata","metadata-type":"remote", // "application":"demo-consumer","sticky":"false","timestamp":"1617676272015","enable-auto-migration":"true"} //為org.apache.dubbo.demo.DemoService接口創建消費方代理對象 ref = createProxy(map); //分發ReferenceConfigInitializedEvent事件 dispatch(new ReferenceConfigInitializedEvent(this, invoker)); } //3.創建動態代理對象 private T createProxy(Map<String, String> map) { //從配置中找出是否應該引用同一JVM中的服務。默認行為為true if (shouldJvmRefer(map)) { //injvm本地引用 URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map); //InjvmProtocol.refer返回InjvmInvoker類型 invoker = REF_PROTOCOL.refer(interfaceClass, url); } else { //遠程引用 //url為配置的peer-to-peer調用地址,或者注冊中心地址 //假如服務集群有5臺機器,你只想測試調用其中的A,B兩臺機器,配置的就是直連提供者AB的地址 //<dubbo:reference id="as" interface="AService" url="dubbo://1.1.1.1:20890;第二提供者地址"/> if (urls.size() == 1) { //一個服務只有一個url,有兩種情況:1.只有一個注冊中心;2.直連一個提供者。 //這里返回的invoker一定是ClusterInvoker子類型,默認為FailoverClusterInvoker, //注冊中心的提供者或者直連url都會轉為普通invoker->directory,再set到ClusterInvoker中返回 //如果url.protocol=service-discovery-registry,對應的protocol為:RegistryProtocol invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)); } else { //一個服務多個url,可能有三種情況:1.多個注冊中心;2.直連多個提供者。3.兩者共存,即url配了注冊中心地址和直連提供者地址,這種情況不確定可不可以,沒試出來。 //如果url中有注冊協議,即從注冊中心訂閱服務,返回的invoker為ZoneAwareClusterInvoker類型 //無注冊協議時,返回FailoverClusterInvoker if (registryURL != null) { String cluster = registryURL.getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME); invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers)); } else { String cluster = "省略..."; invoker = Cluster.getCluster(cluster).join(new StaticDirectory(invokers)); } } } //JavassistProxyFactory.getProxy return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic)); } }
到這就完成了消費者引用服務代理對象,其中Cluster和Invoker的組裝過程,就是上面提到的。
-
補充下master分支中,改版的使用FactoryBean生成代理對象邏輯:
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean { @Override public Object getObject() { //調用ReferenceConfig.get()生成代理對象 return get(); } @Override public Class<?> getObjectType() { //返回服務接口class return getInterfaceClass(); } }