上一章分析了服務暴露的源碼,這一章繼續分析服務引用的源碼。在Dubbo中有兩種引用方式:第一種是服務直連,第二種是基於註冊中心進行引用。服務直連一般用在測試的場景下,線上更多的是基於註冊中心的方式。
服務的引用分爲餓漢式和懶漢式,餓漢即調用ReferenceBean的afterPropertiesSet方法時引用;懶漢即ReferenceBean對應的服務被注入到其他類時引用,也就是用到了這個服務纔會引用。Dubbo默認是懶漢式引用。
同樣的,可以在DubboNamespaceHandler裏面找到服務引用的入口,即ReferenceBean類。入口方法爲getObject:
public Object getObject() throws Exception {
return get();
}
public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
if (ref == null) {
init();
}
return ref;
}
以上兩個方法比較簡單,如果ref不爲空就進入初始化方法。但是這裏有個bug,如果Dubbo源碼在2.6.5以下,debug調試時,ref不爲空,也就不會進入到init方法了。解決方法是:斷點直接打在init方法那一行。或者在IDEA的設置裏面做如下設置:
0.處理配置
進入到init方法,這個方法做的工作比較多,總的來說就是從各種地方獲取配置信息,詳細解釋如下:
private void init() {
//避免重複初始化
if (initialized) {
return;
}
initialized = true;
//檢查接口名是否合法
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
}
// get consumer's global configuration
//略。。。
//從系統變量中獲取與接口名對應的屬性值
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 " + resolveFile + ", cause: " + e.getMessage(), e);
} 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) {
url = resolve;
//打印日誌,略。。。
}
//-------------------------------------------------------------------------------------//
//獲取application、registries、monitor信息,如果沒有就爲null
//略。。。
//檢查application合法性
checkApplication();
checkStubAndMock(interfaceClass);
//-------------------------------------------------------------------------------------//
//添加side、協議版本、時間戳、進程號等信息到map中
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
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) {
logger.warn("NO method found in service interface " + interfaceClass.getName());
map.put("methods", Constants.ANY_VALUE);
} else {
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
//添加前面獲取的各種信息到map中
map.put(Constants.INTERFACE_KEY, interfaceName);
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, this);
//-------------------------------------------------------------------------------------//
//遍歷MethodConfig,處理事件通知配置
//略。。。
//-------------------------------------------------------------------------------------//
//獲取消費者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:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
}
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
//存儲屬性值到系統上下文中,即前面獲取的各種屬性值
StaticContext.getSystemContext().putAll(attributes);
//創建代理對象
ref = createProxy(map);
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}
初始化配置工作大概就這麼多,其中省略了一些代碼,可以通過IDEA調試過一遍。下面的方法纔是關鍵。
1.服務引用
我們進入到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) { // if a url is specified, don't do local reference
isJvmRefer = false;
//根據url的協議、scope、injvm等參數檢查是否需要本地引用
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
// by default, reference local service if there is
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
isJvmRefer = isInjvm().booleanValue();
}
//本地引用
if (isJvmRefer) {
//生成本地引用url,協議爲injvm
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
//構建InjvmInvoker實例
invoker = refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
//遠程引用
} else {
//如果url不爲空,說明可能是直連服務(即我們在調用時寫明瞭Provider的地址)
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
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())) {
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else {
//加載註冊中心url
List<URL> us = loadRegistries(false);
if (us != null && us.size() > 0) {
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 == null || urls.size() == 0) {
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實例
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
//多個註冊中心或者多個服務提供者
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
//獲取所有的Invoker實例
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// use AvailableCluster only when register's cluster is available
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // not a registry url
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
//略。。。
//返回代理對象
return (T) proxyFactory.getProxy(invoker);
}
createProxy方法的主要作用就是先區分是本地引用還是遠程引用,接着區分是直連還是註冊中心、是多註冊中心(服務)還是單註冊中心。最終都會構建出Invoker實例,並返回生成的代理類。Invoker是Dubbo的核心,在消費者這邊,Invoker用於執行遠程調用,其是由Protocol實現類構建而來。Protocol實現類有很多,主要由協議區分,分析服務暴露時我們也看到過。關於消費者這邊的Invoker創建,爲了不影響文章的流暢性,放到最後再分析。
2.創建代理
Invoker創建完畢後,接下來就是爲服務接口生成代理對象。我們找到getProxy的實現方法,在AbstractProxyFactory抽象類中,這個類中的getProxy方法主要是獲取接口列表,最後還會調用一個getProxy的抽象方法,這個抽象方法的實現方法在JavassistProxyFactory類中,如下:
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
//通過newInstance創建代理對象
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
雖然這個Proxy並非Java中的,而是Dubbo自己封裝的,但是依舊是基於Java反射實現的。InvokerInvocationHandler則完全屬於Java動態代理的內容了,主要是實現invoke方法,從而實現調用目標方法。可以在調用目標方法前後添加一些額外的方法,這也是AOP的原理。
疑問解析
-
消費者的Invoker是怎麼創建的?
-
-
從上面的源碼分析中的createProxy方法可以發現,消費者的Invoker是由refer方法構建。不同的協議會進入不同的Protocol實現類中,調用對應的refer方法。debug源碼可以發現,url的協議是registry,我們進入到對應的實現類。
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { //設置協議頭,此時url=zookeeper://192.168.174.128:2181/com.alibaba.dubbo.registry.RegistryService...... 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)) { return doRefer(getMergeableCluster(), registry, type, url); } } //繼續執行服務引用邏輯 return doRefer(cluster, registry, type, url); }
接着進入doRefer方法:
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 URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters); //註冊消費者服務,存在consumer節點 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、configuration、routers等節點數據 directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY + "," + Constants.CONFIGURATORS_CATEGORY + "," + Constants.ROUTERS_CATEGORY)); //註冊中心可能存在多個服務提供者,需要合併爲一個Invoker Invoker invoker = cluster.join(directory); ProviderConsumerRegTable.registerConsuemr(invoker, url, subscribeUrl, directory); return invoker; }
最後,我們啓動消費者,打開Zookeeper客戶端看一下consumer節點下是否有消費者的信息:
-
需要注意一點,要讓消費者保持運行,不然看不到。
-
看到這裏其實有點疑惑,沒看到RegistryProtocol啓動網絡連接,爲什麼就和zk通上信了?其實在上面那個方法中,還調用了DubboProtocol的refer方法,啓動Server就在DubboProtocol中。我們接着分析一下,進入DubboProtocol的refer方法:
-
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException { optimizeSerialization(url); //創建DubboInvoker DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers); invokers.add(invoker); return invoker; }
-
這個方法比較簡單,關鍵在於getClients。它會獲取通信客戶端實例,類型爲ExchangeClient,但是ExchangeClient並不具備通信能力,其底層默認是Netty客戶端。我們看一下getClients方法:
-
private ExchangeClient[] getClients(URL url) { // whether to share connection boolean service_share_connect = false; int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0); // if not configured, connection is shared, otherwise, one connection for one service 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; }
-
getSharedClient方法也會調用initClient方法,我們直接分析一下initClient方法:
-
private ExchangeClient initClient(URL url) { // 獲取客戶端類型,默認爲Netty String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT)); String version = url.getParameter(Constants.DUBBO_VERSION_KEY); boolean compatible = (version != null && version.startsWith("1.0.")); //添加編解碼和心跳包參數到url中 url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME); url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT)); // BIO is not allowed since it has severe performance issue. if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) { throw new RpcException("Unsupported client type: " + str + "," + " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " ")); } ExchangeClient client; try { // connection should be lazy if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) { 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(" + url + "): " + e.getMessage(), e); } return client; }
-
接下來就比較簡單了,可以自己debug一層層跟進connect方法,就會看到NettyClient的構造方法。
-