Dubbo/Dubbox的服務暴露(一)
前言
原始碼入手
平時我要了解一個框架,基本會去從他的Listener入手,如果web.xml中沒有配置listener可能還會有 filter,這是spring給我們的啟示,可是當要去了解dubbo的時候,發現dubbo並沒有自己的listener監聽器。已知dubbo是一款和spring結合較好的rpc框架,那麼其不使用web容器相關的方式,必然遵循spring的方式。依據平時開發經驗,我們知道要想在Spring初始化之後,做一些自己的邏輯,有一種方法即實現org.springframework.beans.factory.InitializingBean
介面,那麼我們搜一下,dubbo有沒有這麼幹。
前文 Dubbo 對配置檔案的解析我們知道,服務(beans->dubbo:service)相關的節點配置會在com.alibaba.dubbo.config.spring.ServiceBean
的例項中儲存,這裡正好巧了public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware
出去一些初始化引數的工作,這裡我們主要關注這裡
public void onApplicationEvent(ApplicationEvent event) {
if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
if (isDelay() && ! isExported() && ! isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
//暴露服務
export();
}
}
}
public synchronized void export() 方法裡會做一些判斷,我們這裡只關注自己最關注的部分
public synchronized void export() {
//.......如果當前服務被暴露過就不再暴露,等一些判斷
if (delay != null && delay > 0) {//delay 延遲暴露引數,如果配置延遲暴露。
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();
}
}
很尷尬,好多原始碼都沒有相關的文件註釋,如果沒參考相關資料,第一次閱讀會很難受
protected synchronized void doExport() 這個方法的作用大概是執行具體的服務暴露
protected synchronized void doExport() {
//......異常處理省略.......//
//檢查provider是否有配置,如果該屬性為null,則new一個ProviderConfig物件,方法內部呼叫appendProperties(provider)方法,該方法內部會拼裝一個 dubbo.tagName.屬性名的key,在配置檔案中查詢值,如果有值則呼叫屬性的setter方法,設定屬性值。
checkDefault();
//如果ProviderConfig例項不為null的情況下,初始化一些配置,通常情況下很少會配置<dubbo:provider/>標籤
//提供方的預設值,當ProtocolConfig和ServiceConfig某屬性沒有配置時,採用此預設值,可選。
if (provider != null) {
//application 對應 <dubbo:application/> 應用配置,用於配置當前應用資訊,不管該應用是提供者還是消費者。
if (application == null) {
application = provider.getApplication();
}
// module 對應<dubbo:module/>模組配置,用於配置當前模組資訊,可選。
if (module == null) {
module = provider.getModule();
}
//registries 對應 <dubbo:registry/> 註冊中心配置,用於配置連線註冊中心相關資訊。
if (registries == null) {
registries = provider.getRegistries();
}
// monitor 對應 <dubbo:monitor/> 監控中心配置,用於配置連線監控中心相關資訊,可選。
if (monitor == null) {
monitor = provider.getMonitor();
}
//protocols 對應 <dubbo:protocol/> 協議配置,用於配置提供服務的協議資訊,協議由提供方指定,消費方被動接受。
if (protocols == null) {
protocols = provider.getProtocols();
}
}
//........省略進一步初始化相關配置的程式碼行 .......//
if (ref instanceof GenericService) { //判斷是否是泛化呼叫介面,這裡不重要。
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
//檢查介面類中是否存在指定的方法,如果dubbo:service->dubbo:method 沒有配置的情況下,methods為null,該方法不會執行方法校驗。如果有相關的配置,該方法會檢查name屬性對應的方法是否存在,不存在會拋IllegalStateException異常。
checkInterfaceAndMethods(interfaceClass, methods);
// 檢查服務實現,如果ref不是服務的介面實現,則會丟擲IllegalStateException異常。
checkRef();
// 宣告該介面非泛化呼叫
generic = Boolean.FALSE.toString();
}
if(local !=null){ //如果是本地服務,常用配置方案下,local 值為null
if(local=="true"){
local=interfaceName+"Local";
}
Class<?> localClass;
try {
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if(!interfaceClass.isAssignableFrom(localClass)){
throw new IllegalStateException("The local implemention class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if(stub !=null){//如果是遠端服務,常用配置方案下,stub值為null
if(stub=="true"){
stub=interfaceName+"Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if(!interfaceClass.isAssignableFrom(stubClass)){
throw new IllegalStateException("The stub implemention class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
//檢查應用配置,如果沒有配置會建立預設物件。其內部會呼叫appendProperties(AbstractConfig config) 填充屬性。檢查失敗會丟擲IllegalStateException異常
checkApplication();
//檢查註冊中心配置,如果沒有配置,則會檢查配置檔案中的dubbo.registry.address 設定,並建立預設物件。其內部會呼叫appendProperties(AbstractConfig config) 填充屬性。檢查失敗會丟擲IllegalStateException異常
checkRegistry();
//檢查協議配置,該方法不會丟擲異常,如果沒配置dubbo:protocol,會取dubbo:provider相關配置填充,如果依舊protocols依舊為null,則建立預設物件。其內部會呼叫appendProperties(AbstractConfig config) 填充屬性。
checkProtocol();
//填充屬性 不多說
appendProperties(this);
//
checkStubAndMock(interfaceClass);
// 初始化path屬性
if (path == null || path.length() == 0) {
path = interfaceName;
}
// 暴露服務的URL
doExportUrls();
}
doExportUrls(); 實現如下(吐槽,懷疑下到假的原始碼了,到處都沒有文件註釋):
@SuppressWarnings({ "unchecked", "rawtypes" })
private void doExportUrls() {
//獲得註冊中心列表
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
protected List loadRegistries(boolean provider) 類相關解析
protected List<URL> loadRegistries(boolean provider) {
// 檢查註冊中心
checkRegistry();
List<URL> registryList = new ArrayList<URL>();
if (registries != null && registries.size() > 0) {
for (RegistryConfig config : registries) {
//一般這個拿到的是註冊中心地址,例如:zookeeper://1.6.5.5:2181
String address = config.getAddress();
if (address == null || address.length() == 0) {
address = Constants.ANYHOST_VALUE;
}
String sysaddress = System.getProperty("dubbo.registry.address");
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
if (address != null && address.length() > 0
&& ! RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
appendParameters(map, application);
appendParameters(map, config);
map.put("path", RegistryService.class.getName());
map.put("dubbo", 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 (! map.containsKey("protocol")) {
//查詢註冊中心是否支援remote協議
if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
map.put("protocol", "remote");
} else {
map.put("protocol", "dubbo");
}
}
//目前到這裡是zookeeper://ip地址:2181/com.alibaba.dubbo.registry.RegistryService?application=配置應用名&dubbo=2.8.4&file=快取檔案路徑&logger=log4j&organization=22222&owner=weiythi&pid=20772×tamp=1510040471140
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
//這個url會被轉換成這種格式 registry://ip:2181/com.alibaba.dubbo.registry.RegistryService?application=配置的應用名&dubbo=2.8.4&file=快取檔案路徑&logger=log4j&organization=22222&owner=weiythi&pid=20772®istry=zookeeper×tamp=1510040471140 即會將註冊中心轉換成registry引數放到url中,並將url的protocol設定成registry
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
|| (! provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs)
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
//獲得協議資訊,預設dubbo
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
String host = protocolConfig.getHost();
if (provider != null && (host == null || host.length() == 0)) {
host = provider.getHost();
}
boolean anyhost = false;
//判斷host是不是為null,或localhost,等本地ip地址
if (NetUtils.isInvalidLocalHost(host)) {
//......省略 使用各種方式獲取本機的實際ip地址........//
host = InetAddress.getLocalHost().getHostAddress();
//......省略 使用各種方式獲取本機的實際ip地址........//
}
Integer port = protocolConfig.getPort();
//.....省略,如果port為null,則嘗試使用預設的port,如果預設的port無法使用,則會嘗試使用隨機的埠
Map<String, String> map = new HashMap<String, String>();
if (anyhost) {
map.put(Constants.ANYHOST_KEY, "true");
}
map.put(Constants.SIDE_KEY, Constants.PROVIDER_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()));
}
// appendParameters 其實就是把屬性值轉到map中,這裡不做說明
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
// 常用配置方案下,methods為null,上文程式碼註釋中有說明
if (methods != null && methods.size() > 0) {
//......省略,當有暴露指定方法的配置時,會暴露指定方法
}
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);
}
//獲取介面中所有的方法名
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 {
//拼接方法名為 a,b,c,d 這種形式
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
if (! ConfigUtils.isEmpty(token)) { //處理token
if (ConfigUtils.isDefault(token)) {
map.put("token", UUID.randomUUID().toString());
} else {
map.put("token", token);
}
}
if ("injvm".equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
// 匯出服務
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
// 注意這裡的URL是com.alibaba.dubbo.common.URL 物件,最後會被處理成一個類似這樣的格式,以dubbo協議為例
//dubbo://ip:port/介面名?其他資訊
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
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) {//遍歷註冊中心
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);
}
//ProxyFactory類的getInvoker方法使用ref生成一個AbstractProxyInvoker例項,到這一步就完成具體服務到Invoker的轉化。接下來就是Invoker轉換到Exporter的過程。
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//Dubbo協議的Invoker轉為Exporter發生在DubboProtocol類的export方法,它主要是開啟socket偵聽服務,並接收客戶端發來的各種請求,通訊細節由Dubbo自己實現。
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
}
} else {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
下一篇集中處理如下程式碼流程,這一篇只是初步瞭解了dubbo 服務暴露預處理的一些邏輯,
至於為什麼dubbo會選擇生成 registry:// 和 dubbo:// 如下兩種連結。以後篇幅討論.
後續將繼續跟蹤以下程式碼片段的實現。
//ProxyFactory類的getInvoker方法使用ref生成一個AbstractProxyInvoker例項,到這一步就完成具體服務到Invoker的轉化。接下來就是Invoker轉換到Exporter的過程。
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//Dubbo協議的Invoker轉為Exporter發生在DubboProtocol類的export方法,它主要是開啟socket偵聽服務,並接收客戶端發來的各種請求,通訊細節由Dubbo自己實現。
Exporter<?> exporter = protocol.export(invoker);
exporters.add(exporter);