文章目錄
在分析完上一節 Dubbo配置文件解析 之後,接下來分析一下 dubbo 的服務導出(註冊)
服務配置類圖
Dubbo服務導出
1、dubbo服務導出入口
服務導出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一個事件響應方法,該方法會在收到 Spring 上下文刷新事件後執行服務導出操作。方法代碼如下:
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
private void doExportUrls() {
// 拼裝url
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
ApplicationModel.initProviderModel(pathKey, providerModel);
// 根據協議進行導出服務
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
2、加載註冊中心鏈接
protected List<URL> loadRegistries(boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<URL>();
if (CollectionUtils.isNotEmpty(registries)) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (StringUtils.isEmpty(address)) {
address = ANYHOST_VALUE;
}
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
appendParameters(map, application);
appendParameters(map, config);
map.put(PATH_KEY, RegistryService.class.getName());
appendRuntimeParameters(map);
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(REGISTRY_PROTOCOL)
.build();
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
循環遍歷 registries,獲取url,並且拼裝參數,獲取到的 registryURLs 如下
string =
“registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=tea-life-provider&check=true&dubbo=2.0.2&pid=7057®istry=zookeeper&release=2.7.3×tamp=1572159342766&version=1.0”
3、導出服務
3.1、組裝Url
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = DUBBO;
}
Map<String, String> map = new HashMap<String, String>();
map.put(SIDE_KEY, PROVIDER_SIDE);
appendRuntimeParameters(map);
appendParameters(map, metrics);
appendParameters(map, application);
appendParameters(map, module);
// remove 'default.' prefix for configs from ProviderConfig
// appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, provider);
appendParameters(map, protocolConfig);
appendParameters(map, this);
解析 metricsConfig、applicationConfig、moduleConfig、providerConfig、protocolConfig、serviceConfig,將解析到的數據放入map
3.2、解析接口中的方法,隨機生成 token
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(METHODS_KEY, ANY_VALUE);
} else {
map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
解析 接口中的方法,將其放入map
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(TOKEN_KEY, token);
}
}
隨機生成token,將其放入map
3.3、服務導出
// export service
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
String scope = url.getParameter(SCOPE_KEY);
// don't export when none is configured
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
// 導出本地服務
if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
// 導出遠程服務
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
}
3.3.1 創建 Invoker
Dubbo 默認的 ProxyFactory 實現類是 JavassistProxyFactory
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
// 創建包裝類,解析接口中的信息
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
// doInvoke的時候,其實就是調用接口中方法的過程
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
3.3.2 Wapper類
個人覺得這個類還是很重要的,Wrapper.getWrapper(Clazz.class)的時候會創建Wapper,將接口的信息解析,存放在
Map<Class<?>, Wrapper> WRAPPER_MAP = new ConcurrentHashMap();
WRAPPER_MAP 對應的是 Class 與 Wraper的關係,Wraper中包含接口的方法,屬性等
public abstract class Wrapper {
public static Wrapper getWrapper(Class<?> c) {
while(ClassGenerator.isDynamicClass(c)) {
c = c.getSuperclass();
}
if (c == Object.class) {
return OBJECT_WRAPPER;
} else {
Wrapper ret = (Wrapper)WRAPPER_MAP.get(c);
if (ret == null) {
ret = makeWrapper(c);
WRAPPER_MAP.put(c, ret);
}
return ret;
}
}
// Wrapper 中包含着方法,屬性,還可以invokeMethod()
private static final Wrapper OBJECT_WRAPPER = new Wrapper() {
public String[] getMethodNames() {
return Wrapper.OBJECT_METHODS;
}
public String[] getDeclaredMethodNames() {
return Wrapper.OBJECT_METHODS;
}
public String[] getPropertyNames() {
return Wrapper.EMPTY_STRING_ARRAY;
}
public Class<?> getPropertyType(String pn) {
return null;
}
public Object getPropertyValue(Object instance, String pn) throws NoSuchPropertyException {
throw new NoSuchPropertyException("Property [" + pn + "] not found.");
}
public void setPropertyValue(Object instance, String pn, Object pv) throws NoSuchPropertyException {
throw new NoSuchPropertyException("Property [" + pn + "] not found.");
}
public boolean hasProperty(String name) {
return false;
}
public Object invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args) throws NoSuchMethodException {
if ("getClass".equals(mn)) {
return instance.getClass();
} else if ("hashCode".equals(mn)) {
return instance.hashCode();
} else if ("toString".equals(mn)) {
return instance.toString();
} else if ("equals".equals(mn)) {
if (args.length == 1) {
return instance.equals(args[0]);
} else {
throw new IllegalArgumentException("Invoke method [" + mn + "] argument number error.");
}
} else {
throw new NoSuchMethodException("Method [" + mn + "] not found.");
}
}
};
其實最後還是調用 Wrapper.invokeMethod()執行
3.3.3 遠程服務導出(包含服務註冊的過程)
// export to remote if the config is not local (export to local only when config is local)
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
if (!isOnlyInJvm() && logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (CollectionUtils.isNotEmpty(registryURLs)) {
for (URL registryURL : registryURLs) {
//if protocol is only injvm ,not register
if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
continue;
}
url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
}
// For providers, this is used to enable custom proxy to generate invoker
String proxy = url.getParameter(PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(PROXY_KEY, proxy);
}
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 這裏進行的是遠程服務的導出,註冊
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else {
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
/**
* @since 2.7.0
* ServiceData Store
*/
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
metadataReportService.publishProvider(url);
}
}
export方法其實幹了兩件事:
1、將上一步拿到的Invoker export出去,然後創建 DubboExporter實例並存儲在 exporterMap 裏,Exporter並沒有什麼玄機,就是一個存儲了Invoker實例及其他各種信息的容器,用於之後獲取Invoker用。
2、打開服務端口,並註冊ExchangeHandler,用於後續響應客戶端網絡請求,響應邏輯這裏先不展開。