Dubbo的啟動主要是釋出服務的過程,起到核心作用的就是ServiceConfig(ServiceConfig就是我們在Dubbo的配置檔案中配置的dubbo:service這些配置項對應的實體類)。服務的啟動初始位置也基本是在這裡,下面我們來看看具體的實作内容。
講基本内容前首先理清楚幾個名詞概念:
Invoker:Invoker的概念我們在動态代理的時候就接觸過,中文的意思大概是執行者,這裡其實可以了解為具體方法的執行者。其核心内容大緻如下:
Class getInterface();
Result invoke(Invocation invocation) throws RpcException;
URL getUrl();
通過以上的三個方法們就可以執行到具體的方法并且獲得方法的執行結果。通過getUrl獲得需要執行的方法具體實作細節,主要是獲得具體的ref;其次就是組裝方法的參數資訊等等,這些資訊在invocation裡面都有封裝;最後通過執行invoke方法觸發具體的方法并傳回結果。從這裡可以看出Invoker是具體方法執行的最後一個守關者,獲得了Invoker,就獲得了具體接口的代碼,然後執行代理就可以。
Invoker僅僅是作為一個代理的門面,其不僅可以代表本地執行Duubo調用的代理,還可以充當RPC時候的代理,更是可以将自己包裝成一個多個Invoker聚合而成的代理(主要是處理叢集的一些政策,包括負載均衡和路由等)。
Exporter:服務暴露的過程中會将Invoker轉換成Exporter(暴露者),及Exporter其實包含了Invoker,主要是用于不同層面的服務釋出。
其實Dubbo 還有一些比較重要的對象,像Protocol,Exchanger等等。我認為在這裡直接說明不太合适,是以等到我們用到之後再開始說明。
-
核心的屬性資訊
一些基本的屬性:group,version,interfaceName,interfaceClass,timeout等等。我們凡是可以在dubbo:service上配置的屬性都在ServiceConfig中可以找得到對應的屬性;
//dubbo對應的服務釋出協定,這裡可以清楚地看到Dubbo在這裡使用的自己的spi機制,來保證靈活性。(至于SPI機制的具體實作,之後有機會的話會講到,簡單了解就是通過getExtensionLoader獲得對應類的擴充類實作類)
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
private final List urls = new ArrayList();
private final List> exporters = new ArrayList>();
2.服務暴露過程
對于服務暴露來說,在ServiceConfig裡面的初始方法就是export()方法了,下面我們從export方法開始來看看:
願意了解架構技術或者源碼的朋友直接加求求(企鵝) 1903832579
public synchronized void export() {
if (provider != null) {
//預設取provider的配置
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
//如果export設定為false的話就直接傳回
if (export != null && ! export.booleanValue()) {
return;
}
//如果設定延遲時間的話就延遲指定時間然後進行暴露
if (delay != null && delay > 0) {
Thread thread = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(delay);
} catch (Throwable e) {
}
doExport();
}
});
thread.setDaemon(true);//将暴露接口的線程設定為守護線程
thread.setName("DelayExportServiceThread");
thread.start();
} else {
doExport(); //一切暴露的核心還都是要看doExport方法。
}
}
protected synchronized void doExport() {
// 防止服務多次暴露
// 設定預設的基本屬性
// 針對泛化接口做單獨處理
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
try {//通過反射初始化接口(interfaceName是實作類的全稱)
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
//檢查定義的方法是否為接口中的方法
checkInterfaceAndMethods(interfaceClass, methods);
//檢查引用不為空,并且引用必需實作接口
checkRef();
//如果到這一步的話說明類實作是自己定義的,是以設定generic為false
generic = Boolean.FALSE.toString();
}
// 處理Local和Stub代理處理
// 檢查Application,Registry,Protocol的配置情況
//将配置的屬性綁定到目前對象
appendProperties(this);
//針對Local,Stub和Mock進行校驗
//上面的操作主要是做一些檢驗和初始化的操作,沒有涉及到具體的暴露服務邏輯
doExportUrls();
}
private void doExportUrls() {
//取到注冊中心的URL
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
//根據配置的通信協定将服務暴露到注冊中心
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 預設采用Dubbo協定
//之後的部分邏輯就是想盡一切辦法取到host
//取到端口号(之後的部分關于端口的邏輯就是想盡一切辦法取端口号)
// 這個map十分重要,它的意義在于所有存儲所有最終使用到的屬性,我們知道一個屬性例如timeout,可能在Application,provider,service中都有配置,具體以哪個為準,都是這個map處理的事情。
Map<String, String> map = new HashMap<String, String>();
if (anyhost) { //如果此時anyhost為true的話
map.put(Constants.ANYHOST_KEY, "true");
}
// 存儲簡單的服務資訊
//将application,module,provider,protocol和service的資訊設定到map裡面
//将應用配置的樹勇按照層級存入map中。注意這裡的層級關系,是一層層覆寫的 即關系為:ServiceConfig->PrtocolConfig->ProviderConfig->ModuleConfig->ApplicaionConfig
//單獨處理好method層級的參數關系
//判斷有沒有配置通配協定
if (ProtocolUtils.isGeneric(generic)) {
map.put("generic", generic);
map.put("methods", Constants.ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
//通過包裝類将interfaceClass進行包裝然後取得方法名字,對于wapper包裝器就是将不同的類統一化
//參考http://blog.csdn.net/quhongwei_zhanqiu/article/details/41597261了解Wapper
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)), ","));
}
}
//處理token屬性
//如果配置的injvm的話就代表本地調用(本地調用還用Dubbo的話實在有點蛋疼)
//所有的核心屬性最後都成了URL的拼接屬性,如果我們還記得map裡面拼裝了多少屬性的話就知道這個URL内容有多豐富
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
// 下面是核心暴露過程,将不會省略源碼
String scope = url.getParameter(Constants.SCOPE_KEY);
//配置為none不暴露
if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
//配置不是remote的情況下做本地暴露 (配置為remote,則表示隻暴露遠端服務)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
//如果配置不是local則暴露為遠端服務.(配置為local,則表示隻暴露遠端服務)
if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (registryURLs != null && registryURLs.size() > 0
&& url.getParameter("register", true)) {
for (URL registryURL : registryURLs) {
//dynamic表示是否需要人工管理服務的上線下線(動态管理模式)
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
====================================================
//取到invoker對象(ref為接口實作類的引用)
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//将invoker轉化為exporter對象
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter); //将exporter添加到需要暴露的清單中取
}
================================================================
} else {
//如果找不到注冊中心的話就自己充當自己的注冊中心吧
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
}
}
}
//多個協定就有多個url與其對應,是以要一一存儲。
this.urls.add(url);
}
通過ServicConfig中的内容分解,我們看出來裡面主要做的内容如下:
檢驗所需參數的合法性
将多層的參數(可能重複配置)最終整理出最終的結果(map),然後根據參數拼接成暴露服務需用到的url。
處理generic,Stub,injvm等其他需要支援的内容,補充dubbo的功能多樣性,但是都不涉及核心流程。
根據對應的協定将服務進行暴露(将提供的服務推送到注冊中心供服務調用者發現),預設使用Dubbo協定。