本文將以 註解暴露服務
的方式探究Motan服務的註冊過程。java
以 @MotanService
註解標記的類,在應用啓動時,會被Motan掃描,並做爲服務的具體實現註冊到註冊中心中。node
就像下面這樣:git
@MotanService(export = "demoMotan:8002")
public class MotanDemoServiceImpl implements MotanDemoService {
@Override
public String hello(String name) {
System.out.println(name);
return "Hello " + name + "!";
}
@Override
public User rename(User user, String name) throws Exception {
Objects.requireNonNull(user);
System.out.println(user.getId() + " rename " + user.getName() + " to " + name);
user.setName(name);
return user;
}
}
複製代碼
在 Motan如何完成與Spring的集成 一文中已經說過應用啓動時,是如何掃描到 @MotanService
註解標記的類的,這裏再也不贅述。github
@MotanService
的解析過程在 com.weibo.api.motan.config.springsupport.AnnotationBean
類中的 postProcessAfterInitialization(Object bean, String beanName)
方法實現。spring
首先會解析配置信息,例如這個服務實現的接口是誰、以及application
、moodule
、group
、version
、filter
等的配置信息,最後會將這些信息封裝到 com.weibo.api.motan.config.springsupport.ServiceConfigBean
的對象中。api
先來看一下 ServiceConfigBean
的UML圖:併發
圖中最左側的繼承關係是 ServiceConfigBean
自身的繼承關係,右側的是Spring的相關擴展。這個圖這裏先有個印象,咱們繼續上面的思路走。app
Motan將配置信息封裝到 ServiceConfigBean
後,調用了 afterPropertiesSet()
方法,由上圖可知,這個方法是 InitializingBean
接口中抽象方法的實現。jvm
這個方法其實就幹了下面三件事兒:ide
@Override
public void afterPropertiesSet() throws Exception {
// 檢查並配置basicConfig
checkAndConfigBasicConfig();
// 檢查是否已經裝配export,若是沒有則到basicConfig查找
checkAndConfigExport();
// 檢查並配置registry
checkAndConfigRegistry();
}
複製代碼
basicService
屬性是否爲空,若是是空,須要從新解析並設置他的值。如何從新設置他的值呢?從UML圖中咱們能夠找到 basicService
的位置,在 ServiceConfig
類中的第7個Field。字段類型是 BasicServiceInterfaceConfig
。這個檢查其實就是找到當前Spring容器中全部 BasicServiceInterfaceConfig
類型的bean,若是隻找到一個,就把這個賦值到 basicService
上,若是有多個,須要找到 BasicServiceInterfaceConfig
的 isDefault
屬性爲true的那個,並賦值。
export
的值是否已經設置,若是沒有設置,到 basicService
中查找。這一步實際上是檢查 protocol
,也就是 motan
、motan2
這些協議是否已經設置好,export字段的格式爲:protocol1:port1,protocol2:port2
。對應到UML中,export字段在 AbstractServiceConfig
類中。 這裏同時會將 export
的值解析到 AbstractInterfaceConfig
的 protocols
字段中。
例如 zookeeper
、consul
這些是否已經配置好。若是是空的話,仍是從 basicService
中查找,並將結果配置到 AbstractInterfaceConfig
的 registries
屬性中。
這些都檢查好之後,basicService
、export
、protocols
、registries
這些字段就初始化好了。而後會將新建立出來的這個 ServiceConfigBean
實例添加到 AnnotationBean
的 serviceConfigs
屬性中。
private final Set<ServiceConfigBean<?>> serviceConfigs = new ConcurrentHashSet<ServiceConfigBean<?>>();
複製代碼
至此 @MotanService
解析完成,能夠準備發佈並註冊服務了。
完成上述的解析和初始化後,會調用 ServiceConfigBean
的 export()
方法來發布並註冊服務。
serviceConfig.export();
複製代碼
其實現以下:
public synchronized void export() { // 這裏加了個併發的控制,鎖使用的是 this
// 若是已經發布過了,直接返回
if (exported.get()) {
LoggerUtil.warn(String.format("%s has already been expoted, so ignore the export request!", interfaceClass.getName()));
return;
}
// 檢查暴露服務的類是不是某個接口的實現,若是不是則拋出異常
// 檢查暴露的方法是否在接口中存在,若是沒有則拋出異常
checkInterfaceAndMethods(interfaceClass, methods);
// 解析註冊中心地址,並將host、port等參數封裝到URL類中
List<URL> registryUrls = loadRegistryUrls();
if (registryUrls == null || registryUrls.size() == 0) {
throw new IllegalStateException("Should set registry config for service:" + interfaceClass.getName());
}
// 解析協議和服務暴露的端口,默認爲 `motan` 協議。Map的結構爲:<協議, 端口>
Map<String, Integer> protocolPorts = getProtocolAndPort();
for (ProtocolConfig protocolConfig : protocols) {
Integer port = protocolPorts.get(protocolConfig.getId());
if (port == null) {
throw new MotanServiceException(String.format("Unknow port in service:%s, protocol:%s", interfaceClass.getName(),
protocolConfig.getId()));
}
// 註冊並暴露服務
doExport(protocolConfig, port, registryUrls);
}
afterExport();
}
複製代碼
PS:URL這個類是Motan本身定義的類,Motan中幾乎全部跟URL相關的東西都用它封裝。
上述代碼先初始化了暴露服務以前須要的一些數據:註冊中心地址、服務協議、暴露端口等,真正執行服務註冊的是 doExport
方法。這個方法較長,這裏只貼出關鍵部分。
private void doExport(ProtocolConfig protocolConfig, int port, List<URL> registryURLs) {
// ... 省略 ...
// 省略部分代碼主要做用是處理下面這行URL中的參數,例如:protocolName -> motan,hostAddress -> 本機IP,port -> 暴露端口 等
// map是解析出來的配置,以及一些默認配置,例如:
/* "haStrategy" -> "failover" "module" -> "ad-common" "check" -> "false" "nodeType" -> "service" "version" -> "1.1.0" "filter" -> "cafTracing,pepperProfiler,sentinelProfiler" "minWorkerThread" -> "20" "retries" -> "1" "protocol" -> "motan" "application" -> "ad-common" "maxWorkerThread" -> "200" "shareChannel" -> "true" "refreshTimestamp" -> "1571821305290" "id" -> "ad-commonBasicServiceConfigBean" "export" -> "ad-commonProtocolConfigBean:8022" "requestTimeout" -> "30000" "group" -> "ad-common" */
URL serviceUrl = new URL(protocolName, hostAddress, port, interfaceClass.getName(), map);
// 校驗服務是否已經存在
// 註冊完成的服務會添加到一個set中,serviceExists方法就是檢查這個set中是否已經包含了這個服務的描述符(描述符的格式大概是host、port、protocol、version、nodeType組合的字符串)
// serviceUrl就是這個東西:motan://192.168.100.14:8022/com.coohua.ad.common.remote.api.AdCommonRPC?group=ad-common
if (serviceExists(serviceUrl)) {
LoggerUtil.warn(String.format("%s configService is malformed, for same service (%s) already exists ", interfaceClass.getName(),
serviceUrl.getIdentity()));
throw new MotanFrameworkException(String.format("%s configService is malformed, for same service (%s) already exists ",
interfaceClass.getName(), serviceUrl.getIdentity()), MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
}
List<URL> urls = new ArrayList<URL>();
// injvm 協議只支持註冊到本地,其餘協議能夠註冊到local、remote
if (MotanConstants.PROTOCOL_INJVM.equals(protocolConfig.getId())) {
// ... 省略,主要關注下面的註冊中心暴露服務
} else {
for (URL ru : registryURLs) {
urls.add(ru.createCopy()); // 這裏是一個淺拷貝,只是new了一個URL,具體字段用的仍是以前的引用。
}
}
// registereUrls 是註冊中心的URL
for (URL u : urls) {
u.addParameter(URLParamType.embed.getName(), StringTools.urlEncode(serviceUrl.toFullStr()));
registereUrls.add(u.createCopy());
}
ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);
// 到註冊中心註冊服務,urls是註冊中心的地址
exporters.add(configHandler.export(interfaceClass, ref, urls));
}
複製代碼
最後調用 configHandler.export
註冊時,通過上面的解析過程,url的parameters參數中已經包含了註冊須要用到的信息,例如:
"path" -> "com.weibo.api.motan.registry.RegistryService"
"address" -> "192.168.103.254:2181"
"application" -> null
"name" -> "direct"
"connectTimeout" -> "3000"
"id" -> "ad-commonRegistryConfigBean"
"refreshTimestamp" -> "1571821250310"
"embed" -> "motan%3A%2F%2F192.168.100.14%3A8022%2Fcom.coohua.ad.common.remote.api.AdCommonRPC%3FhaStrategy%3Dfailover%26module%3Dad-common%26check%3Dfalse%26nodeType%3Dservice%26version%3D1.1.0%26filter%3DcafTracing%2CpepperProfiler%2CsentinelProfiler%26minWorkerThread%3D20%26retries%3D1%26protocol%3Dmotan%26application%3Dad-common%26maxWorkerThread%3D200%26shareChannel%3Dtrue%26refreshTimestamp%3D1571821305290%26id%3Dad-commonBasicServiceConfigBean%26export%3Dad-commonProtocolConfigBean%3A8022%26requestTimeout%3D30000%26group%3Dad-common%26"
"requestTimeout" -> "1000"
複製代碼
接下來看一下 configHandler.export
作了什麼事情。
public <T> Exporter<T> export(Class<T> interfaceClass, T ref, List<URL> registryUrls) {
// 解碼url -> motan://192.168.100.14:8022/com.coohua.ad.common.remote.api.AdCommonRPC?group=ad-common
String serviceStr = StringTools.urlDecode(registryUrls.get(0).getParameter(URLParamType.embed.getName()));
URL serviceUrl = URL.valueOf(serviceStr);
// export service
String protocolName = serviceUrl.getParameter(URLParamType.protocol.getName(), URLParamType.protocol.getValue());
// SPI的方式拿到具體的Protocol實現,默認狀況下拿到 motan 的 Protocol
Protocol orgProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(protocolName);
Provider<T> provider = getProvider(orgProtocol, ref, serviceUrl, interfaceClass);
Protocol protocol = new ProtocolFilterDecorator(orgProtocol);
// 在這裏走Motan的filter chain,並啓動服務,filter chain經過調用 ProtocolFilterDecorator 的 decorateWithFilter 方法實現
// 走完filter chain後,會調用 orgProtocol 的 export 方法來暴露服務,這個方法的實如今 AbstractProtocol 類中
Exporter<T> exporter = protocol.export(provider, serviceUrl);
// 在註冊中心中註冊服務
register(registryUrls, serviceUrl);
return exporter;
}
複製代碼
在 AbstractProtocol
的 export
方法中會調用 createExporter
方法建立一個 Exporter
類的實例(具體來講是 DefaultRpcExporter
),在這個建立過程當中會調用 NettyEndpointFactory
的 createServer
方法建立一個Server出來,並存放在exporter的server變量中。
而後調用 exporter
的 init
方法,在 init
方法中又調用了 doInit
方法,這個方法調用了 server.open()
,至此,服務啓動,並監聽在本機指定的端口上。
@Override
protected boolean doInit() {
boolean result = server.open();
return result;
}
複製代碼
此時服務已經成功啓動了,但還沒註冊到註冊中心,因此還不能被發現。接下來,繼續上面的代碼,看一下 register(registryUrls, serviceUrl);
這行代碼幹了啥。
此時兩個參數的值分別是:
這個方法的實現以下:
private void register(List<URL> registryUrls, URL serviceUrl) {
for (URL url : registryUrls) {
// 根據protocol的名稱獲取具體的 RegistryFactory ,這裏以 zookeeper 爲例
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(url.getProtocol());
if (registryFactory == null) {
throw new MotanFrameworkException(new MotanErrorMsg(500, MotanErrorMsgConstant.FRAMEWORK_REGISTER_ERROR_CODE,
"register error! Could not find extension for registry protocol:" + url.getProtocol()
+ ", make sure registry module for " + url.getProtocol() + " is in classpath!"));
}
// 嘗試獲取url對應registry已有的實例,若是沒有,就建立一個
// 這裏zookeeper是用ZkClient管理的
Registry registry = registryFactory.getRegistry(url);
// 在zk中建立Node,完成服務的註冊
registry.register(serviceUrl);
}
}
複製代碼
ZK中的註冊結果:
[zk: localhost:2181(CONNECTED) 0] ls /motan/ad-common/com.coohua.ad.common.remote.api.AdCommonRPC/server
[192.168.100.14:8022]
複製代碼
至此,服務就能夠被調用方發現了。
最後轉載一張圖總結一下上面的過程