文章目錄
服務消費端應該做哪些事情
- 生成一個代理對象(幫我們實現網絡通信的細節)
- 建立通信連接(netty)
- 從zk去獲取目標地址(訂閱節點的變化)
- 負載均衡
- 容錯
- mock降級策略
- 序列化
引用遠程服務的兩種方式:
- dubbo直連(開發,測試環境)
- 基於註冊中心
重點分析通過註冊中心引用服務的過程,主要包括從註冊中心獲取配置,Invoker創建,代理類創建
服務引用原理
服務引用時機:
- Spring容器調用ReferenceBean的afterPropertiesSet方法時引用服務(餓漢式)
- 在ReferenceBean對應的服務被注入到其他類中時引用。(懶漢式,默認的)
按默認配置來分析懶漢式引用:
入口是ReferenceBean的getObject()開始,當我們的服務被注入到其他類中時候,spring會第一時間調用getObject()方法,該方法來會執行引用邏輯。執行邏輯之前需要先進行配置檢查與收集工作,然後根據收集到的配置信息來決定服務引用的方式;
- 引用本地JVM服務(在同一個jvm上,不需要走遠端調用)
- 通過直連方式引用遠程服務
- 通過註冊中心來引用遠程服務。
以上三種引用方式都會得到一個Invoker實例,如有多個註冊中心,多個服務提供者會得到一組Invoker實例,此時需要Cluster將多個Invoker合併成一個實例。合併後的Invoker實例具備調用本地和遠程服務的能力,但不能將此時離暴露給用戶使用,如此會對用戶業務代碼造成侵入,所以需要通過代理工程類(ProxyFactory)爲服務接口生成代理類,並讓代理類去調用Invoker邏輯,避免Dubbo框架代碼對業務代碼造成侵入。
源碼分析
1. ReferenceBean 的 getObject()
// 該方法定義在 Spring 的 FactoryBean 接口
public Object getObject() throws Exception {
return get();
}
public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
// 檢測 ref 是否爲空,爲空則通過 init 方法創建
if (ref == null) {
// init 方法主要用於處理配置,以及調用 createProxy 生成代理類
init();
}
return ref;
}
1.1 處理配置
與服務端暴露服務一樣,init裏面首先會對dubbo的配置進行檢查和處理,以保證配置的正確性。配置解析的邏輯都在ReferenceConfig 的init方法中
private void init() {
// ------------------------------------------------------------
// 這一部分代碼用於檢測ConsumerConfig實例是否存在,如果不存在則創建新的實例
// 通過系統變量或dubbo.properties配置文件填充ConsumerConfig的字段
// 檢查泛化配置,並根據配置設置interfaceClass的值。
// 避免重複初始化
if (initialized) {
return;
}
initialized = true;
// 檢測接口名合法性
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("interface not allow null!");
}
// 檢測 consumer 變量是否爲空,爲空則創建
checkDefault();
appendProperties(this);
if (getGeneric() == null && getConsumer() != null) {
// 設置 generic
setGeneric(getConsumer().getGeneric());
}
// 檢測是否爲泛化接口
if (ProtocolUtils.isGeneric(getGeneric())) {
interfaceClass = GenericService.class;
} else {
try {
// 加載類
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, methods);
}
// -------------------------------✨ 分割線1 ✨------------------------------
// 這份的主要邏輯用於從該系統屬性或配置文件中加載與接口名相對應的配置,並將解析結果賦值給url字段
// url字段的租用一般是用於點對點調試
// 從系統變量中獲取與接口名對應的屬性值
String resolve = System.getProperty(interfaceName);
String resolveFile = null;
if (resolve == null || resolve.length() == 0) {
// 從系統屬性中獲取解析文件路徑
resolveFile = System.getProperty("dubbo.resolve.file");
if (resolveFile == null || resolveFile.length() == 0) {
// 從指定位置加載配置文件
File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
if (userResolveFile.exists()) {
// 獲取文件絕對路徑
resolveFile = userResolveFile.getAbsolutePath();
}
}
if (resolveFile != null && resolveFile.length() > 0) {
Properties properties = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(new File(resolveFile));
// 從文件中加載配置
properties.load(fis);
} catch (IOException e) {
throw new IllegalStateException("Unload ..., cause:...");
} finally {
try {
if (null != fis) fis.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
// 獲取與接口名對應的配置
resolve = properties.getProperty(interfaceName);
}
}
if (resolve != null && resolve.length() > 0) {
// 將 resolve 賦值給 url
url = resolve;
}
// -------------------------------✨ 分割線2 ✨------------------------------
// 這部分代碼用於檢測幾個核心配置類是否爲空,爲空則嘗試從其他配置類中獲取
if (consumer != null) {
if (application == null) {
// 從 consumer 中獲取 Application 實例,下同
application = consumer.getApplication();
}
if (module == null) {
module = consumer.getModule();
}
if (registries == null) {
registries = consumer.getRegistries();
}
if (monitor == null) {
monitor = consumer.getMonitor();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
// 檢測 Application 合法性
checkApplication();
// 檢測本地存根配置合法性
checkStubAndMock(interfaceClass);
// -------------------------------✨ 分割線3 ✨------------------------------
// 這部分代碼用於手機各種配置,並將配置存儲到map中
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
// 添加 side、協議版本信息、時間戳和進程號等信息到 map 中
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 非泛化服務
if (!isGeneric()) {
// 獲取版本
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
// 獲取接口方法列表,並添加到 map 中
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
map.put("methods", Constants.ANY_VALUE);
} else {
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
map.put(Constants.INTERFACE_KEY, interfaceName);
// 將 ApplicationConfig、ConsumerConfig、ReferenceConfig 等對象的字段信息添加到 map 中
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, this);
// -------------------------------✨ 分割線4 ✨------------------------------
// 用於處理MethodConfig實例
// 該實例包含了事件通知配置,onreturn ,onthrow, oninvoke等。
String prefix = StringUtils.getServiceKey(map);
if (methods != null && !methods.isEmpty()) {
// 遍歷 MethodConfig 列表
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
// 檢測 map 是否包含 methodName.retry
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
// 添加重試次數配置 methodName.retries
map.put(method.getName() + ".retries", "0");
}
}
// 添加 MethodConfig 中的“屬性”字段到 attributes
// 比如 onreturn、onthrow、oninvoke 等
appendAttributes(attributes, method, prefix + "." + method.getName());
checkAndConvertImplicitConfig(method, map, attributes);
}
}
// -------------------------------✨ 分割線5 ✨------------------------------
// 這部分代碼主要用於解析服務消費者ip,以及調用createProxy創建代理對象
// 獲取服務消費者 ip 地址
String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
if (hostToRegistry == null || hostToRegistry.length() == 0) {
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property..." );
}
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
// 存儲 attributes 到系統上下文中
StaticContext.getSystemContext().putAll(attributes);
// 創建代理類
ref = createProxy(map);
// 根據服務名,ReferenceConfig,代理類構建 ConsumerModel,
// 並將 ConsumerModel 存入到 ApplicationModel 中
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}
這部分的主要邏輯,用分割線分割了這幾大塊:
- 第一部分代碼用於檢測ConsumerConfig實例是否存在,如果不存在則創建新的實例,通過系統變量或dubbo.properties配置文件填充ConsumerConfig的字段檢查泛化配置,並根據配置設置interfaceClass的值。
- 第二部分主要邏輯用於從該系統屬性或配置文件中加載與接口名相對應的配置,並將解析結果賦值給url字段
url字段的租用一般是用於點對點調試
- 第三部分代碼用於檢測幾個核心配置類是否爲空,爲空則嘗試從其他配置類中獲取
- 第四部分代碼用於手機各種配置,並將配置存儲到map中
- 第五部分用於處理MethodConfig實例,該實例包含了事件通知配置,onreturn ,onthrow, oninvoke等。
- 最後一部分代碼主要用於解析服務消費者ip,以及調用createProxy創建代理對象
1.2 引用服務
ReferenceConfig.createProxy()開始,這個方法不僅會創建一個代理對象,而且還會調用其他方法構建以及合併invoker實例。
private T createProxy(Map<String, String> map) {
URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
if (isInjvm() == null) {
// url 配置被指定,則不做本地引用
if (url != null && url.length() > 0) {
isJvmRefer = false;
// 根據 url 的協議、scope 以及 injvm 等參數檢測是否需要本地引用
// 比如如果用戶顯式配置了 scope=local,此時 isInjvmRefer 返回 true
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
// 獲取 injvm 配置值
isJvmRefer = isInjvm().booleanValue();
}
// 本地引用
if (isJvmRefer) {
// 生成本地引用 URL,協議爲 injvm
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
// 調用 refer 方法構建 InjvmInvoker 實例
invoker = refprotocol.refer(interfaceClass, url);
// 遠程引用
} else {
// url 不爲空,表明用戶可能想進行點對點調用
if (url != null && url.length() > 0) {
// 當需要配置多個 url 時,可用分號進行分割,這裏會進行切分
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
// 設置接口全限定名爲 url 路徑
url = url.setPath(interfaceName);
}
// 檢測 url 協議是否爲 registry,若是,表明用戶想使用指定的註冊中心
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
// 將 map 轉換爲查詢字符串,並作爲 refer 參數的值添加到 url 中
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
// 合併 url,移除服務提供者的一些配置(這些配置來源於用戶配置的 url 屬性),
// 比如線程池相關配置。並保留服務提供者的部分配置,比如版本,group,時間戳等
// 最後將合併後的配置設置爲 url 查詢字符串中。
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else {
// 加載註冊中心 url
List<URL> us = loadRegistries(false);
if (us != null && !us.isEmpty()) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
// 添加 refer 參數到 url 中,並將 url 添加到 urls 中
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
// 未配置註冊中心,拋出異常
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference...");
}
}
// 單個註冊中心或服務提供者(服務直連,下同)
if (urls.size() == 1) {
// 調用 RegistryProtocol 的 refer 構建 Invoker 實例
invoker = refprotocol.refer(interfaceClass, urls.get(0));
// 多個註冊中心或多個服務提供者,或者兩者混合
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
// 獲取所有的 Invoker
for (URL url : urls) {
// 通過 refprotocol 調用 refer 構建 Invoker,refprotocol 會在運行時
// 根據 url 協議頭加載指定的 Protocol 實例,並調用實例的 refer 方法
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url;
}
}
if (registryURL != null) {
// 如果註冊中心鏈接不爲空,則將使用 AvailableCluster
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
// 創建 StaticDirectory 實例,並由 Cluster 對多個 Invoker 進行合併
invoker = cluster.join(new StaticDirectory(u, invokers));
} else {
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
Boolean c = check;
if (c == null && consumer != null) {
c = consumer.isCheck();
}
if (c == null) {
c = true;
}
// invoker 可用性檢查
if (c && !invoker.isAvailable()) {
throw new IllegalStateException("No provider available for the service...");
}
// 生成代理類
return (T) proxyFactory.getProxy(invoker);
}
上述代碼的邏輯:
- 首先根據配置檢查是否爲本地調用,如果是本地調用則調用InjvmProtocol生成injvmInvoker實例
- 若非本地調用,則讀取直連配置項,或註冊中心url,將讀取到的url存儲到urls裏面,
- 判斷urls的數量,若=1,直接通過Protocol自適應擴展類構建Invoker實例。
- 若urls數量>1,表示存在多個註冊中心或服務直連url,此時根據url構建invoker,然後通過cluster合併多個invoker,最後調用ProxyFactory生成代理類
1.2.1 創建Invoker
Invoker是Dubbo的核心模型,代表一個可執行體。
在服務提供方:Invoker用於調用服務提供類。
在服務消費方:Invoker用於執行遠程調用。
Invoker是由Protocol實現類構建而來,Protocol實現類有很多,RegistryProtocol和DubboProtocol,根據不同協議對應不同實現類。現在默認分析dubbo協議的實現類DubboProtocol;
DubboProtocol.refer()
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
// 創建 DubboInvoker
// getClients用於獲取客戶端實例,實例類型爲ExchangeClient
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
getClients用於獲取客戶端實例,實例類型爲ExchangeClient,但ExchangeClient實際上並不具備通信能力,dubbo默認使用NettyClient實例進行通信;
getClients
private ExchangeClient[] getClients(URL url) {
// 是否共享連接
boolean service_share_connect = false;
// 獲取連接數,默認爲0,表示未配置
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
// 如果未配置 connections,則共享連接
if (connections == 0) {
service_share_connect = true;
connections = 1;
}
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (service_share_connect) {
// 獲取共享客戶端
clients[i] = getSharedClient(url);
} else {
// 初始化新的客戶端
clients[i] = initClient(url);
}
}
return clients;
}
connections數量決定是獲取共享客戶端還是創建愛你新的客戶端實例,默認情況下,使用共享客戶端實例。下面來分析獲取共享客戶端實例的過程:
DubboProtocol.getSharedClient()
先訪問緩存,若緩存未命中,則通過initClient方法創建新的ExchangeClient實例
private ExchangeClient getSharedClient(URL url) {
String key = url.getAddress();
// 獲取帶有“引用計數”功能的 ExchangeClient
// 先訪問緩存,若緩存未命中,則通過initClient方法創建新的ExchangeClient實例
ReferenceCountExchangeClient client = referenceClientMap.get(key);
if (client != null) {
if (!client.isClosed()) {
// 增加引用計數
client.incrementAndGetCount();
return client;
} else {
referenceClientMap.remove(key);
}
}
locks.putIfAbsent(key, new Object());
synchronized (locks.get(key)) {
if (referenceClientMap.containsKey(key)) {
return referenceClientMap.get(key);
}
// 創建 ExchangeClient 客戶端
ExchangeClient exchangeClient = initClient(url);
// 將 ExchangeClient 實例傳給 ReferenceCountExchangeClient,這裏使用了裝飾模式
client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
referenceClientMap.put(key, client);
ghostClientMap.remove(key);
locks.remove(key);
return client;
}
}
** DubboProtocol.initClient(url)**
創建 ExchangeClient 客戶端
initClient 方法首先獲取用戶配置的客戶端類型,默認爲netty,檢測用戶配置的客戶端類型是否存在,不存在則拋異常。
private ExchangeClient initClient(URL url) {
// 獲取客戶端類型,默認爲 netty
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
// 添加編解碼和心跳包參數到 url 中
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// 檢測客戶端類型是否存在,不存在則拋出異常
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported client type: ...");
}
ExchangeClient client;
try {
// 獲取 lazy 配置,並根據配置值決定創建的客戶端類型
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
// 創建懶加載 ExchangeClient 實例
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
// 創建普通 ExchangeClient 實例
client = Exchangers.connect(url, requestHandler);
}
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service...");
}
return client;
}
** Exchangers.connect(url, requestHandler)**
創建 ExchangeClient 客戶端,
public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
// 獲取 Exchanger 實例,默認爲 HeaderExchangeClient
// getExchanger 會通過 SPI 加載 HeaderExchangeClient 實例
return getExchanger(url).connect(url, handler);
}
HeaderExchangeClient.connect
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
// 這裏包含了多個調用,分別如下:
// 1. 創建 HeaderExchangeHandler 對象
// 2. 創建 DecodeHandler 對象
// 3. 通過 Transporters 構建 Client 實例
// 4. 創建 HeaderExchangeClient 對象
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
Transporters.connect()
public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
ChannelHandler handler;
if (handlers == null || handlers.length == 0) {
handler = new ChannelHandlerAdapter();
} else if (handlers.length == 1) {
handler = handlers[0];
} else {
// 如果 handler 數量大於1,則創建一個 ChannelHandler 分發器
handler = new ChannelHandlerDispatcher(handlers);
}
// 獲取 Transporter 自適應拓展類,並調用 connect 方法生成 Client 實例
// 這裏默認加載NettyTransporter並調用該類的connect方法
return getTransporter().connect(url, handler);
}
NettyTransporter.connect()
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
// 創建 NettyClient 對象
return new NettyClient(url, listener);
}
DubboProtocol的refer()到這裏執行完了,再次回到RegistryProtocol的refer邏輯
RegistryProtocol.refer()
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 取 registry 參數值,並將其設置爲協議頭
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
// 獲取註冊中心實例
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// 將 url 查詢字符串轉爲 Map
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
// 獲取 group 配置
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
|| "*".equals(group)) {
// 通過 SPI 加載 MergeableCluster 實例,並調用 doRefer 繼續執行服務引用邏輯
return doRefer(getMergeableCluster(), registry, type, url);
}
}
// 調用 doRefer 繼續執行服務引用邏輯
return doRefer(cluster, registry, type, url);
}
首先爲url設置協議頭,然後根據url參數加載註冊中心實例,然後獲取group配置,根據group配置決定doRefer第一個參數的類型
核心方法 RegistryProtocol.doRefer()
創建 RegistryDirectory 實例
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// 創建 RegistryDirectory 實例
//1. 構建一個協議地址保存到zk, consumer://ip:port
//2. 從註冊中戲獲取到地址(providerUrl)
//3. 建立連接
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
// 設置註冊中心和協議
directory.setRegistry(registry);
directory.setProtocol(protocol);
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
// 生成服務消費者鏈接
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
// 註冊服務消費者,在 consumers 目錄下新節點
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
// 訂閱 providers、configurators、routers 等節點數據
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
/**********************************/
// 到這已經建立好了通信連接,開始考慮負載和目標調用
// 一個註冊中心可能有多個服務提供者,因此這裏需要將多個服務提供者合併爲一個
Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
主要邏輯:
- doRefer ()創建 RegistryDirectory 實例,然後生成服務者消費者鏈接,並向註冊中心進行註冊。
- 註冊完畢,訂閱provider,configurators,routers等節點下的數據。
- 完成訂閱後,RegistryDirectory會收到這幾個節點下子節點的信息,由於一個服務可能部署在多臺服務器上,這樣provider下就會產生多個節點,cluster會將多個服務節點合併成一個,生成一個Invoker
1.2.2 創建代理
Invoker創建完畢後,需要爲服務接口生成代理對象,有了代理對象即可進行遠程通信。代理對象生成的入口方法爲ProxyFactory的getProxy。
public <T> T getProxy(Invoker<T> invoker) throws RpcException {
// 調用重載方法
return getProxy(invoker, false);
}
public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException {
Class<?>[] interfaces = null;
// 獲取接口列表
String config = invoker.getUrl().getParameter("interfaces");
if (config != null && config.length() > 0) {
// 切分接口列表
String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
if (types != null && types.length > 0) {
interfaces = new Class<?>[types.length + 2];
// 設置服務接口類和 EchoService.class 到 interfaces 中
interfaces[0] = invoker.getInterface();
interfaces[1] = EchoService.class;
for (int i = 0; i < types.length; i++) {
// 加載接口類
interfaces[i + 1] = ReflectUtils.forName(types[i]);
}
}
}
if (interfaces == null) {
interfaces = new Class<?>[]{invoker.getInterface(), EchoService.class};
}
// 爲 http 和 hessian 協議提供泛化調用支持,參考 pull request #1827
if (!invoker.getInterface().equals(GenericService.class) && generic) {
int len = interfaces.length;
Class<?>[] temp = interfaces;
// 創建新的 interfaces 數組
interfaces = new Class<?>[len + 1];
System.arraycopy(temp, 0, interfaces, 0, len);
// 設置 GenericService.class 到數組中
interfaces[len] = GenericService.class;
}
// 調用重載方法 創建代理類
return getProxy(invoker, interfaces);
}
public abstract <T> T getProxy(Invoker<T> invoker, Class<?>[] types);
getProxy中大部分的邏輯是通過反射機制獲取interface數組,最後調用了getProxy()
Dubbo框架默認JavassistProxyFactory 來生成代理類,下面看一下JavassistProxyFactory對getProxy的實現;
// 創建代理類
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
// 生成 Proxy 子類(Proxy 是抽象類)。並調用 Proxy 子類的 newInstance 方法創建 Proxy 實例
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
- 通過Proxy的getProxy方法獲取Proxy子類,然後創建InvokerInvocationHandler對象,並將該對象傳給newInstance生成的Proxy實例。
- InvokerInvocationHandler實現了自jdk的InvocationHandler接口,具體的用途是攔截接口類調用。