在這個版本中dubbo會通過注解@PostConstruct
把ServiceBean
實例放到ConfigManager
中
public abstract class AbstractConfig implements Serializable {
@PostConstruct
public void addIntoConfigManager() {
ApplicationModel.getConfigManager().addConfig(this);
}
......
}
DubboBootstrapApplicationListener
會監聽spring容器啟動發布的ContextRefreshedEvent
,遍歷ConfigManager
中的ServiceBean
并調用export
方法。
public class DubboBootstrapApplicationListener extends OnceApplicationContextEventListener implements Ordered {
@Override
public void onApplicationContextEvent(ApplicationContextEvent event) {
if (DubboBootstrapStartStopListenerSpringAdapter.applicationContext == null) {
DubboBootstrapStartStopListenerSpringAdapter.applicationContext = event.getApplicationContext();
}
//spring容器啟動完事件
if (event instanceof ContextRefreshedEvent) {
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
}
..........省略代碼
}
調用ServiceBean
的export
方法前,會初始化配置的參數,默認配置的優先級為:
JVM環境變量->操作系統環境變量->配置中心APP配置->配置中心Global配置->本地dubbo.properties文件配置中心配置->dubbo.properties文件配置。(這里還沒有涉及動態配置,在服務注冊后會監聽動態配置,這時候動態配置優先級最高)
最后根據配置文件的優先級對ServiceBean
對中的屬性進行賦值。
執行到暴露服務的代碼前有較多與主流程不相關的邏輯,直接跳過。調用流程如下:
org.apache.dubbo.config.bootstrap.DubboBootstrap#exportServices
--> org.apache.dubbo.config.bootstrap.DubboBootstrap#exportService
--> org.apache.dubbo.config.ServiceConfig#export
--> org.apache.dubbo.config.ServiceConfig#doExport
--> org.apache.dubbo.config.ServiceConfig#doExportUrls
private void doExportUrls() {
.............省略部分代碼
//獲得注冊中心地址
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
int protocolConfigNum = protocols.size();
// 根據協議循環注冊(dubbo,http)
for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
// In case user specified path, register service one more time to map it to path.
repository.registerService(pathKey, interfaceClass);
doExportUrlsFor1Protocol(protocolConfig, registryURLs, protocolConfigNum);
}
}
doExportUrls
主要做兩點
- 獲取注冊中心地址。
- 根據協議注冊。
doExportUrlsFor1Protocol
代碼比較多,我這里刪減了大部分代碼,只留下關鍵邏輯
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs, int protocolConfigNum) {
.....省略
URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
.....省略
exportLocal(url);
.....省略
if (CollectionUtils.isNotEmpty(registryURLs)) {
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);
}
.....省略
}
- 根據拼接的map生成提供者的URL
- 進行本地暴露
- 進行遠程暴露
本地暴露
private void exportLocal(URL url) {
URL local = URLBuilder.from(url)
//替換protocol替換為injvm
.setProtocol(LOCAL_PROTOCOL)
//設置host為127.0.0.1
.setHost(LOCALHOST_VALUE)
.setPort(0)
.build();
Exporter<?> exporter = PROTOCOL.export(
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
}
exportLocal處理步驟為
- 替換protocol為injvm和host為127.0.0.1
-
PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local)
生成invoker
3.PROTOCOL.export()
進行本地暴露
PROXY_FACTORY
是dubbo
的Adaptive
類,默認SPI擴展為javassist
。
private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
public class JavassistProxyFactory extends AbstractProxyFactory {
。。。。。。。。。。。。省略
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// 范圍Wrapper 的實現類
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
//這里可以理解invokeMethod調用的是我們提供者具體的業務處理類。
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}
入參proxy
是ServiceBean
中的ref,指向的是我們服務提供者的業務處理類(如demoServiceImpl
)。getInvoker
首先是動態生成Wrapper
的實現類。然后返回一個AbstractProxyInvoker
匿名內部類,
wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments)
可以理解調用的具體的服務提供者業務處理類。
調用PROTOCOL.export()進行本地服務暴露。
PROTOCOL
也是dubbo的Adaptiv
類,因為protocol
為injvm
所以最終調用的是InjvmProtocol
的export方法
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
public class InjvmProtocol extends AbstractProtocol implements Protocol{
public static final String NAME = LOCAL_PROTOCOL;
public static final int DEFAULT_PORT = 0;
private static InjvmProtocol INSTANCE;
public InjvmProtocol() {
INSTANCE = this;
}
public static InjvmProtocol getInjvmProtocol() {
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
//本地暴露serviceKey就是接口的全限定類名。如org.apache.dubbo.demo.DemoService
String serviceKey = invoker.getUrl().getServiceKey();
// 將invoker ,serviceKey 封裝在 InjvmExporter
InjvmExporter<T> tInjvmExporter = new InjvmExporter<>(invoker, serviceKey, exporterMap);
// 放入exporterMap
exporterMap.addExportMap(serviceKey, tInjvmExporter);
return tInjvmExporter;
}
}
本地暴露的邏輯比較簡單serviceKey
就是接口的全限定類名org.apache.dubbo.demo.DemoService
。
最后invoker被封裝在InjvmExporter
,并serviceKey
為key存儲在exporterMap
遠程暴露
遠程暴露與本地暴露一樣,調用的也是JavassistProxyFactory
的getInvoker
方法。但傳入的URL是注冊中心+服務提供者URL
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass,
registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
// DelegateProviderMetaDataInvoker也表示服務提供者,包括了Invoker和服務的配置
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
- .注冊中心URL: registry://127.0.0.1:2181/xxxxxxxxxx
- 服務提供者URL : dubbo://192.168.2.19:20880/xxxxxxxxxx
- 調用 registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())合并后的URL。export后面的是拼裝了服務提供者URL部分。registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&
export=dubbo%3A%2F%2F192.168.2.19%3A20880%xxxxxxxxxx
其中對應的各自SPI處理類如下
registry:// ---> RegistryProtocol
zookeeper:// ---> ZookeeperRegistry
dubbo:// ---> DubboProtocol
合并URL后先執行RegistryProtocol
的export
方法
public class RegistryProtocol implements Protocol {
..........省略
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// registry://xxx?xx=xx®istry=zookeeper 轉換為 zookeeper://xxx?xx=xx 表示注冊中心
URL registryUrl = getRegistryUrl(originInvoker);
// 得到服務提供者url,表示服務提供者
URL providerUrl = getProviderUrl(originInvoker);
// overrideSubscribeUrl是老版本的動態配置監聽url,表示了需要監聽的服務以及監聽的類型(configurators, 這是老版本上的動態配置)
// 在服務提供者url的基礎上,生成一個overrideSubscribeUrl,協議為provider://,增加參數category=configurators&check=false
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
// 一個overrideSubscribeUrl對應一個OverrideListener,用來監聽變化事件,監聽到overrideSubscribeUrl的變化后,
// OverrideListener就會根據變化進行相應處理,具體處理邏輯看OverrideListener的實現
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// 在這個方法里會利用providerConfigurationListener和serviceConfigurationListener去重寫providerUrl
// providerConfigurationListener表示應用級別的動態配置監聽器,providerConfigurationListener是RegistyProtocol的一個屬性
// serviceConfigurationListener表示服務級別的動態配置監聽器,serviceConfigurationListener是在每暴露一個服務時就會生成一個
// 這兩個監聽器都是新版本中的監聽器
// 新版本監聽的zk路徑是:
// 服務: /dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators節點的內容
// 應用: /dubbo/config/dubbo/dubbo-demo-provider-application.configurators節點的內容
// 注意,要喝配置中心的路徑區分開來,配置中心的路徑是:
// 應用:/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService/dubbo.properties節點的內容
// 全局:/dubbo/config/dubbo/dubbo.properties節點的內容
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// 根據動態配置重寫了providerUrl之后,就會調用DubboProtocol或HttpProtocol去進行導出服務了
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// // 得到注冊中心-ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
//得到存入到注冊中心去的providerUrl,會對服務提供者url中的參數進行簡化
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
//是否需要注冊到注冊中心
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 注冊服務,把簡化后的服務提供者url注冊到registryUrl中去
registry.register(registeredProviderUrl);
}
// 針對老版本的動態配置,需要把overrideSubscribeListener綁定到overrideSubscribeUrl上去進行監聽
registerStatedUrl(registryUrl, registeredProviderUrl, register);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
// Deprecated! Subscribe to override rules in 2.6.x or before.
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
notifyExport(exporter);
//Ensure that a new exporter instance is returned every time export
return new DestroyableExporter<>(exporter);
}
}
這是服務暴露比較重要分方法,主要步驟如下
1.獲取注冊中心的URL,并把 registry://xxx?xx=xx®istry=zookeeper 轉換為 zookeeper://xxx?xx=xx 表示注冊中心
- 得到服務提供者url,表示服務提供者。
- 構建老板本dubbo監聽的URL.
overrideSubscribeUrl
是老版本的動態配置監聽url,表示了需要監聽的服務以及監聽的類型(configurators
, 這是老版本上的動態配置) - 監聽動態配置,其中動態配置分為應用動態配置和服務動態配置并重寫
providerUrl
。(注意不是配置中心)
服務配置路徑: /dubbo/config/dubbo/org.apache.dubbo.demo.DemoService.configurators
應用配置路徑: /dubbo/config/dubbo/dubbo-demo-provider-application.configurators - 根據動態配置重寫了
providerUrl
之后,就會調用DubboProtocol
或HttpProtocol
去進行導出服務了 - 得到注冊中心實現-
ZookeeperRegistry
- 注冊服務,把簡化后的服務提供者url注冊到
registryUrl
中去 - 得到存入到注冊中心去的
providerUrl
,會對服務提供者url中的參數進行簡化 - 針對老版本的動態配置,需要把
overrideSubscribeListener
綁定到overrideSubscribeUrl
上去進行監聽
監聽動態配置和重寫提供者URL
private URL overrideUrlWithConfig(URL providerUrl, OverrideListener listener) {
// 應用配置,providerConfigurationListener是在屬性那里直接初始化好的,providerConfigurationListener會監聽配置中心的應用配置信息變動
providerUrl = providerConfigurationListener.overrideUrl(providerUrl);
// 服務配置,new ServiceConfigurationListener的時候回初始化,ServiceConfigurationListener會監聽配置中心的服務信息配置信息變動
ServiceConfigurationListener serviceConfigurationListener = new ServiceConfigurationListener(providerUrl, listener);
serviceConfigurationListeners.put(providerUrl.getServiceKey(), serviceConfigurationListener);
return serviceConfigurationListener.overrideUrl(providerUrl);
}
應用動態配置由 ProviderConfigurationListener
實現,它是RegistryProtocol
的一個成員變量,在RegistryProtocol
實例化是同時也實例化ProviderConfigurationListener
,在ProviderConfigurationListener
的構造函數中調用initWith
方法獲取應用動態配置(initWith在AbstractConfiguratorListener
類構造方法中調用,AbstractConfiguratorListener
也是ServiceConfigurationListener
的父類,所以服務監聽實例化時也會調用這個方法獲取服務動態配置)。
protected final void initWith(String key) {
// 添加Listener,進行了訂閱
ruleRepository.addListener(key, this);
// 從配置中心ConfigCenter獲取屬于當前應用的動態配置數據,從zk中拿到原始數據(主動從配置中心獲取數據)
String rawConfig = ruleRepository.getRule(key, DynamicConfiguration.DEFAULT_GROUP);
if (!StringUtils.isEmpty(rawConfig)) {
// 如果存在應用配置信息則根據配置信息生成Configurator
genConfiguratorsFromRawRule(rawConfig);
}
}
在overrideUrlWithConfig
方法中首先根據應用動態配置調用overrideUrl
方法重寫提供者URL,再實例化ServiceConfigurationListener
獲取服務動態配置,調用overrideUrl
方法重寫提供者URL。
服務導出
在doLocalExport
方法中,會調用protocol.export()
服務導出,默認使用的是DubboProtocol
public class DubboProtocol extends AbstractProtocol {
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// export service.
String key = serviceKey(url);
// 構造一個Exporter進行服務導出
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.addExportMap(key, exporter);
......省略
// 開啟NettyServer
openServer(url);
// 特殊的一些序列化機制,比如kryo提供了注冊機制來注冊類,提高序列化和反序列化的速度
optimizeSerialization(url);
return exporter;
}
}
把傳入的Invoker封裝成DubboExporter
放入到exporterMap
中。然后開啟Netty Server。
開啟netty流程不在贅述了,有興趣可以執行閱讀,流程如下。
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#openServer
-->org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#createServer
--> org.apache.dubbo.remoting.exchange.Exchangers#bind
--> org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#bind
--> org.apache.dubbo.remoting.Transporters#bind
--> org.apache.dubbo.remoting.transport.netty.NettyTransporter#bind
--> org.apache.dubbo.remoting.transport.netty.NettyServer
--> org.apache.dubbo.remoting.transport.netty.NettyServer#doOpen
服務注冊
服務注冊根據不同的注冊中心有不同實現,ZK實現為ZookeeperRegistry
,所以 registry.register(registeredProviderUrl)方法最終會調用到ZookeeperRegistry
的doRegister
方法。
public void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
至此,服務導出源碼解析的源碼就分析完了