1. 環境搭建
-
代碼已經上傳至 https://github.com/masteryourself/dubbo ,分支名稱是
masteryourself-2.7.3-release
-
provider 是
dubbo-demo-xml-provider
工程,啓動類是Application
-
consumer 是
dubbo-demo-xml-consumer
工程,啓動類是Application
2. 源碼解析
2.1 關於 ServiceBean
- 由於
ServiceBean
實現了ApplicationListener<ContextRefreshedEvent>
接口,所以會在onApplicationEvent
方法中監聽ContextRefreshedEvent
事件,當容器刷新完畢後,會調用export()
方法完成服務暴露
2.2 流程預覽
// 1. 監聽 spring 的 【ContextRefreshedEvent】 事件
org.apache.dubbo.config.spring.ServiceBean#onApplicationEvent ->
// 調用 export() 方法完成服務暴露
org.apache.dubbo.config.spring.ServiceBean#export ->
// 調用父類 【ServiceConfig】 完成服務暴露
org.apache.dubbo.config.ServiceConfig#export ->
// 1.1 檢查和更新屬性
org.apache.dubbo.config.ServiceConfig#checkAndUpdateSubConfigs ->
// 設置全局的默認屬性,因爲 serviceBean 和 application、registries 等配置類都有關聯關係
org.apache.dubbo.config.ServiceConfig#completeCompoundConfigs
// 啓動全局配置中心
org.apache.dubbo.config.AbstractInterfaceConfig#startConfigCenter ->
// 刷新 ApplicationConfig、MonitorConfig、ModuleConfig、ProtocolConfig、RegistryConfig、ProviderConfig、ConsumerConfig 配置
org.apache.dubbo.config.context.ConfigManager#refreshAll
// 1.1.1(*) 獲取混合配置,給 set 開頭的方法賦值,即完成屬性賦值
// 配置優先級默認是:系統環境變量 -> 配置中心某個應用的配置 -> 配置中心的全局配置 -> AbstractConfig 類的屬性值 ->
// dubbo.properties.file 或 dubbo.properties 文件配置
org.apache.dubbo.config.AbstractConfig#refresh
// 1.1.1.1(*) 初始化 compositeConfiguration 混合配置 list
org.apache.dubbo.common.config.Environment#getConfiguration
// 1.2 導出服務
org.apache.dubbo.config.ServiceConfig#doExport
// 1.2.1(*) 循環所有的 protocols,依次進行服務暴露,這裏的 protocols 有 2 個,因此一共會暴露 4 個地址
// 0 = {ProtocolConfig@2823} "<dubbo:protocol name="dubbo" port="20880" valid="true" id="dubbo" prefix="dubbo.protocols." />"
// 1 = {ProtocolConfig@2840} "<dubbo:protocol name="dubbo" port="20881" valid="true" id="dubbo2" prefix="dubbo.protocols." />"
org.apache.dubbo.config.ServiceConfig#doExportUrls
// 1.2.1.1 獲取要導出的服務 url,格式如下:
// 0 = {URL@2835} "registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&
// dubbo=2.0.2&pid=7208&qos-port=22222®istry=zookeeper×tamp=1577007384243"
// 1 = {URL@2836} "registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&
// dubbo=2.0.2&pid=7208&qos-port=22222®istry=zookeeper×tamp=1577007384245&version=1.1.0"
org.apache.dubbo.config.AbstractInterfaceConfig#loadRegistries
// 1.2.1.2(*) 調用此方法進行服務暴露
org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol
// 1.2.1.2.1(*) 改寫 url,把協議改成 injvm 協議,host 設置爲 127.0.0.1,端口號設置爲 0
// 如果配置了 remote,則表示禁止使用 injvm 協議,就不會進行本地暴露
org.apache.dubbo.config.ServiceConfig#exportLocal ->
// 調用 Protocol 的動態代理類 【Protocol$Adaptive】 的 export 方法,先經過 wrapper 包裝類,再到 【InjvmProtocol】,因爲協議被改成了 injvm
org.apache.dubbo.rpc.Protocol$Adaptive#export ->
// ProtocolListener 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 injvm,所以會起作用
// 服務導出之後,可以用監聽器做一些操作
org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper#export ->
// ProtocolFilter 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 injvm,所以會起作用
org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#export
// 1.2.1.2.1.1(*) 構建 filterChain,實現類最終會被包裝成
// EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter -> TraceFilter -> TimeoutFilter ->
// MonitorFilter -> ExceptionFilter -> DemoServiceImpl
org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#buildInvokerChain
// InjvmProtocol,真正處理 injvm 協議的 protocol 類
org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol#export ->
// 在構造方法裏把 invoker 添加到了 【exporterMap】 屬性中,在 injvm 協議調用時會從此 map 取值
// "org.apache.dubbo.demo.DemoService" -> "org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$CallbackRegistrationInvoker@5827af16"
org.apache.dubbo.rpc.protocol.injvm.InjvmExporter#<init>
// 1.2.1.2.2 循環所有的 registryURLs,作遠程服務導出,形如:
// registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&
// export=dubbo%3A%2F%2F192.168.89.1%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3D
// demo-provider%26bean.name%3Dorg.apache.dubbo.demo.DemoService%26bind.ip%3D192.168.89.1%26bind.port%3D20880%26
// deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26
// methods%3DsayHello%26pid%3D18816%26qos-port%3D22222%26register%3Dtrue%26release%3D%26side%3Dprovider%26
// timestamp%3D1577009068166&pid=18816&qos-port=22222®istry=zookeeper×tamp=1577009067710
// 調用 Protocol 的動態代理類 【Protocol$Adaptive】 的 export 方法,先經過 wrapper 包裝類,再到 【RegistryProtocol】,因爲協議是 registry
org.apache.dubbo.rpc.Protocol$Adaptive#export ->
// ProtocolListener 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 registry,所以不會起作用
// 服務導出之後,可以用監聽器做一些操作
org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper#export ->
// ProtocolFilter 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 registry,所以不會起作用
org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#export
// 1.2.1.2.2.1(*) 添加監聽器,暴露遠程服務,連接 zk 創建節點
org.apache.dubbo.registry.integration.RegistryProtocol#export
// 1.2.1.2.2.1.1 暴露遠程服務,形如:
// dubbo://192.168.89.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&
// bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.89.1&bind.port=20880&deprecated=false&
// dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&
// pid=8724&qos-port=22222®ister=true&release=&side=provider×tamp=1577011539455
org.apache.dubbo.registry.integration.RegistryProtocol#doLocalExport ->
// 調用 Protocol 的動態代理類 【Protocol$Adaptive】 的 export 方法,先經過 wrapper 包裝類,再到 【DubboProtocol】
org.apache.dubbo.rpc.Protocol$Adaptive#export ->
// ProtocolListener 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 dubbo,所以會起作用
// 服務導出之後,可以用監聽器做一些操作
org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper#export ->
// ProtocolFilter 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 dubbo,所以會起作用同 【1.2.1.2.1.1】
org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#export ->
// 獲取 url,暴露 netty 遠程服務
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export ->
// 獲取 netty server,綁定服務名稱和端口號
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#openServer
// 1.2.1.2.2.1.2 把服務註冊到 zk 上
org.apache.dubbo.registry.integration.RegistryProtocol#register ->
// 調用 zookeeper 的 doRegister 方法,把服務路徑寫到 zk 節點上
org.apache.dubbo.registry.support.FailbackRegistry#doRegister
2.3 流程詳解
2.3.1 AbstractConfig#refresh(1.1.1)
org.apache.dubbo.config.AbstractConfig
public void refresh() {
try {
// 獲取混合配置
// 1. 系統環境變量
// 2. 配置中心某個應用的配置
// 3. 配置中心的全局配置
// 4. dubbo.properties.file 或 dubbo.properties 文件配置
CompositeConfiguration compositeConfiguration = Environment.getInstance().getConfiguration(getPrefix(), getId());
// 獲取 AbstractConfig 類中屬性的值,即調用 getXxx 或 isXxx 方法返回的所有 metaData 值
InmemoryConfiguration config = new InmemoryConfiguration(getPrefix(), getId());
config.addProperties(getMetaData());
// 判斷是否是配置中心的配置優先
// 配置優先級默認是:系統環境變量 -> 配置中心某個應用的配置 -> 配置中心的全局配置 -> AbstractConfig 類的屬性值 ->
// dubbo.properties.file 或 dubbo.properties 文件配置
if (Environment.getInstance().isConfigCenterFirst()) {
// The sequence would be: SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
compositeConfiguration.addConfiguration(3, config);
} else {
// The sequence would be: SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration
compositeConfiguration.addConfiguration(1, config);
}
// loop methods, get override value and set the new value back to method
Method[] methods = getClass().getMethods();
for (Method method : methods) {
// 獲取 set 開頭的方法
if (MethodUtils.isSetter(method)) {
try {
// 根據 setXxx 的 xxx 屬性,從 compositeConfiguration 混合配置中獲取屬性對應的值
String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
// isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig.
if (StringUtils.isNotEmpty(value) && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)) {
// 反射調用 set 方法賦值
method.invoke(this, ClassUtils.convertPrimitive(method.getParameterTypes()[0], value));
}
} catch (NoSuchMethodException e) {
logger.info("Failed to override the property " + method.getName() + " in " +
this.getClass().getSimpleName() +
", please make sure every property has getter/setter method provided.");
}
}
}
} catch (Exception e) {
logger.error("Failed to override ", e);
}
}
2.3.2 Environment#getConfiguration(1.1.1.1)
org.apache.dubbo.common.config.Environment
public CompositeConfiguration getConfiguration(String prefix, String id) {
CompositeConfiguration compositeConfiguration = new CompositeConfiguration();
// Config center has the highest priority
// 從系統環境變量中獲取值
compositeConfiguration.addConfiguration(this.getSystemConfig(prefix, id));
// 從配置中心的 dubbo.config.xxx.dubbo.properties 節點獲取值(xxx 應用的配置)
compositeConfiguration.addConfiguration(this.getAppExternalConfig(prefix, id));
// 從配置中心的 dubbo.config.dubbo.dubbo.properties 節點獲取值(全局配置)
compositeConfiguration.addConfiguration(this.getExternalConfig(prefix, id));
// 從 dubbo.properties.file 或者 dubbo.properties 文件中獲取值
compositeConfiguration.addConfiguration(this.getPropertiesConfig(prefix, id));
return compositeConfiguration;
}
2.3.3 ServiceConfig#doExportUrls(1.2.1)
org.apache.dubbo.config.ServiceConfig
private void doExportUrls() {
// 獲取要導出的服務 url,格式如下
// 0 = {URL@2835} "registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&
// dubbo=2.0.2&pid=7208&qos-port=22222®istry=zookeeper×tamp=1577007384243"
// 1 = {URL@2836} "registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&
// dubbo=2.0.2&pid=7208&qos-port=22222®istry=zookeeper×tamp=1577007384245&version=1.1.0"
List<URL> registryURLs = loadRegistries(true);
// 循環所有的 protocols,挨個導出服務,protocols 有如下兩個,所以一共會暴露 4 個服務
// 0 = {ProtocolConfig@2823} "<dubbo:protocol name="dubbo" port="20880" valid="true" id="dubbo" prefix="dubbo.protocols." />"
// 1 = {ProtocolConfig@2840} "<dubbo:protocol name="dubbo" port="20881" valid="true" id="dubbo2" prefix="dubbo.protocols." />"
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.3.4 ServiceConfig#doExportUrlsFor1Protocol(1.2.1.2)
org.apache.dubbo.config.ServiceConfig
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 獲取協議名稱,如果是空,默認爲 dubbo 協議
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);
...
// export service
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
// 構造一個要導出的 url
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
...
if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
// export to local if the config is not remote (export to remote only when config is remote)
// 如果配置了 remote,則表示禁止使用 injvm 協議,就不會進行本地暴露
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)) {
if (!isOnlyInJvm() && logger.isInfoEnabled()) {
logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
}
if (CollectionUtils.isNotEmpty(registryURLs)) {
// 循環所有的 registryURLs
for (URL registryURL : registryURLs) {
...
// 暴露遠程服務
// registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.89.1%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bean.name%3Dorg.apache.dubbo.demo.DemoService%26bind.ip%3D192.168.89.1%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D21564%26qos-port%3D22222%26register%3Dtrue%26release%3D%26side%3Dprovider%26timestamp%3D1577013554161&pid=21564&qos-port=22222®istry=zookeeper×tamp=1577013552772
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else {
...
}
...
}
}
this.urls.add(url);
}
2.3.5 ServiceConfig#exportLocal(1.2.1.2.1)
org.apache.dubbo.config.ServiceConfig
private void exportLocal(URL url) {
// 改寫 url,把協議改成 injvm 協議,host 設置爲 127.0.0.1,端口號設置爲 0
URL local = URLBuilder.from(url)
.setProtocol(LOCAL_PROTOCOL)
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
// 本地服務導出
Exporter<?> exporter = protocol.export(
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
2.3.6 ProtocolFilterWrapper#buildInvokerChain(1.2.1.2.1.1)
org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
// 獲取所有激活的 filter Extension
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
// 循環包裝
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public URL getUrl() {
return invoker.getUrl();
}
@Override
public boolean isAvailable() {
return invoker.isAvailable();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult;
try {
asyncResult = filter.invoke(next, invocation);
} catch (Exception e) {
// onError callback
if (filter instanceof ListenableFilter) {
Filter.Listener listener = ((ListenableFilter) filter).listener();
if (listener != null) {
listener.onError(e, invoker, invocation);
}
}
throw e;
}
return asyncResult;
}
@Override
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return new CallbackRegistrationInvoker<>(last, filters);
}
2.3.7 RegistryProtocol#export(1.2.1.2.2.1)
org.apache.dubbo.registry.integration.RegistryProtocol#export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 獲取註冊中心地址
// zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.89.1%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bean.name%3Dorg.apache.dubbo.demo.DemoService%26bind.ip%3D192.168.89.1%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D18816%26qos-port%3D22222%26register%3Dtrue%26release%3D%26side%3Dprovider%26timestamp%3D1577009068166&pid=18816&qos-port=22222×tamp=1577009067710
URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally
// 獲取導出地址
// dubbo://192.168.89.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.89.1&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=18816&qos-port=22222®ister=true&release=&side=provider×tamp=1577009068166
URL providerUrl = getProviderUrl(originInvoker);
// Subscribe the override data
// FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
// the same service. Because the subscribed is cached key with the name of the service, it causes the
// subscription information to cover.
// 獲取能覆蓋配置的訂閱地址
// provider://192.168.89.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.89.1&bind.port=20880&category=configurators&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=1108&qos-port=22222®ister=true&release=&side=provider×tamp=1577010446361
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
// 添加監聽器
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
//export invoker
// 調用 originInvoker 的 protocol 實現,進行對應協議的服務暴露,這裏是【DubboProtocol】
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// url to registry
// 獲取 registry,這裏是 【ZookeeperRegistry】
final Registry registry = getRegistry(originInvoker);
// 簡化 url,因爲新版本多了配置中心
final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
registryUrl, registeredProviderUrl);
//to judge if we need to delay publish
// 判斷是否需要註冊
boolean register = registeredProviderUrl.getParameter("register", true);
if (register) {
// 服務註冊
register(registryUrl, registeredProviderUrl);
providerInvokerWrapper.setReg(true);
}
// Deprecated! Subscribe to override rules in 2.6.x or before.
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}