0 前言
Dubbo是阿里巴巴開源的基于Java實現的高性能、透明化的RPC框架。深入了解Dubbo源碼,有助于快速定位問題、高效實現自定義拓展。本文以Dubbo服務端初始化過程為例,分析Dubbo怎么從配置轉化成可被調用的服務。
以典型的服務端結合Spring配置為例:
<!-- 提供方應用信息,用于計算依賴關系 -->
<dubbo:application name="demo-provider"/>
<!-- 用dubbo協議在20880端口暴露服務 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 使用zookeeper注冊中心暴露服務地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:1234" id="registry"/>
<!-- 默認的服務端配置 -->
<dubbo:provider registry="registry" retries="0" timeout="5000"/>
<!-- 和本地bean一樣實現服務 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
<!-- 聲明需要暴露的服務接口 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
在Dubbo命名空間下定義了一系列XML節點,如:application
、protocol
、registry
、provider
、service
等,Dubbo通過實現Spring提供的 NamespaceHandler
接口,向Spring注冊 BeanDefinition
解析器,使Spring能識別Dubbo命名空間下的節點,并且通過實現 BeanDefinitionParser
接口,使Spring能解析各節點的具體配置。
DubboNamespaceHandler#init() ,源碼如下:
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
}
由以上代碼可以看出,各個節點最終被轉化為各種Bean,配置的各種屬性也被轉化為Bean的屬性。從Bean的類型可以看出,大部分Bean只用于提供Dubbo的運行參數,只有 ServiceBean
才是本文服務發布分析入口。
備注:
DubboNamespaceHandler.java
&DubboBeanDefinitionParser.java
源碼分析,請參考《☆聊聊Dubbo(四):核心源碼-切入Spring》一文。
1 ServiceBean 核心入口
Dubbo服務提供者由 dubbo:service
來定義的,從前面可以看到,Spring把 dubbo:service
解析成一個ServiceBean,ServiceBean實現了 ApplicationListener
和 InitializingBean
接口,ServiceBean有個核心方法 export
,在這個方法中初始化服務提供者并且暴露遠程服務。這個方法在bean初始化或容器中所有bean刷新完畢時被調用,根據 provider
的延遲設置決定,如果設置了延遲( delay
屬性)則在bean初始化結束之后調用,否則在刷新事件中被調用,默認會延遲 export
,即在所有bean的刷新結束被調用。
在 com.alibaba.dubbo.config.spring.ServiceBean
類,源碼如下:
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware {
...
public void afterPropertiesSet() {}
...
public void onApplicationEvent(ApplicationEvent event) {}
...
public void destroy() {}
}
ServiceBean
實現了Spring的 InitializingBean
、DisposableBean
、 ApplicationListener
等接口,實現了 afterPropertiesSet()
、 destroy()
、 onApplicationEvent()
等典型方法,這里便是Dubbo和Spring整合的關鍵,一般第三方框架基本都是通過這幾個接口和Spring整合的。
afterPropertiesSet()
主要用來注入各種 ConfigBean
,便于服務注冊過程中各種參數的獲取,注意看最后關于延遲發布的幾行代碼,大意是如果不延遲,就立即注冊和暴露服務。
ServiceBean#afterPropertiesSet(),源碼如下:
public void afterPropertiesSet() throws Exception {
// @ step1
if (getProvider() == null) {
// BeanFactoryUtils.beansOfTypeIncludingAncestors 究竟做了什么?
// 返回指定類型和子類型的所有bean,若該bean factory 是一個繼承類型的beanFactory,這個方法也會獲取祖宗factory中定義的指定類型的bean。
Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);
if (providerConfigMap != null && providerConfigMap.size() > 0) {
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if ((protocolConfigMap == null || protocolConfigMap.size() == 0)
&& providerConfigMap.size() > 1) { // backward compatibility
List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() != null && config.isDefault()) {
providerConfigs.add(config);
}
}
if (!providerConfigs.isEmpty()) {
setProviders(providerConfigs);
}
} else {
ProviderConfig providerConfig = null;
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
if (providerConfig != null) {
throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);
}
providerConfig = config;
}
}
if (providerConfig != null) {
setProvider(providerConfig);
}
}
}
}
// @ step2
if (getApplication() == null
&& (getProvider() == null || getProvider().getApplication() == null)) {
Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
ApplicationConfig applicationConfig = null;
for (ApplicationConfig config : applicationConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
if (applicationConfig != null) {
throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
}
applicationConfig = config;
}
}
if (applicationConfig != null) {
setApplication(applicationConfig);
}
}
}
// @ step3
if (getModule() == null
&& (getProvider() == null || getProvider().getModule() == null)) {
Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
ModuleConfig moduleConfig = null;
for (ModuleConfig config : moduleConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
if (moduleConfig != null) {
throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
}
moduleConfig = config;
}
}
if (moduleConfig != null) {
setModule(moduleConfig);
}
}
}
// @ step4
if ((getRegistries() == null || getRegistries().isEmpty())
&& (getProvider() == null || getProvider().getRegistries() == null || getProvider().getRegistries().isEmpty())
&& (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().isEmpty())) {
Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
if (registryConfigMap != null && registryConfigMap.size() > 0) {
List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
for (RegistryConfig config : registryConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
registryConfigs.add(config);
}
}
if (!registryConfigs.isEmpty()) {
super.setRegistries(registryConfigs);
}
}
}
// @ step5
if (getMonitor() == null
&& (getProvider() == null || getProvider().getMonitor() == null)
&& (getApplication() == null || getApplication().getMonitor() == null)) {
Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
MonitorConfig monitorConfig = null;
for (MonitorConfig config : monitorConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
if (monitorConfig != null) {
throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
}
monitorConfig = config;
}
}
if (monitorConfig != null) {
setMonitor(monitorConfig);
}
}
}
// @ step6
if ((getProtocols() == null || getProtocols().isEmpty())
&& (getProvider() == null || getProvider().getProtocols() == null || getProvider().getProtocols().isEmpty())) {
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if (protocolConfigMap != null && protocolConfigMap.size() > 0) {
List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
for (ProtocolConfig config : protocolConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
protocolConfigs.add(config);
}
}
if (!protocolConfigs.isEmpty()) {
super.setProtocols(protocolConfigs);
}
}
}
// @ step7
if (getPath() == null || getPath().length() == 0) {
if (beanName != null && beanName.length() > 0
&& getInterface() != null && getInterface().length() > 0
&& beanName.startsWith(getInterface())) {
setPath(beanName);
}
}
// @ step8
if (!isDelay()) {
export();
}
}
Step1:如果
provider
為空,說明dubbo:service
標簽未設置provider
屬性,則嘗試從BeanFactory
中查詢dubbo:provider
實例,如果存在一個dubbo:provider
標簽,則取該實例,如果存在多個dubbo:provider
配置則provider
屬性不能為空,否則拋出異常:“Duplicate provider configs”。Step2:如果
application
為空,說明dubbo:service
標簽未設置application
屬性,則嘗試從BeanFactory
中查詢dubbo:application
實例,如果存在一個dubbo:application
標簽,則取該實例,如果存在多個dubbo:application
配置,則拋出異常:“Duplicate application configs”。Step3:如果
module
為空,說明dubbo:service
標簽未設置module
屬性,則嘗試從BeanFactory
中查詢dubbo:module
實例,如果存在一個dubbo:module
標簽,則取該實例,如果存在多個dubbo:module
,則拋出異常:“Duplicate module configs”。Step4:(邏輯同上)嘗試從
BeanFactory
中加載所有的注冊中心,注意ServiceBean
的List registries
屬性,為注冊中心集合。Step5:(邏輯同上)嘗試從
BeanFacotry
中加載一個監控中心,填充ServiceBean
的MonitorConfig monitor
屬性,如果存在多個dubbo:monitor
配置,則拋出”Duplicate monitor configs: “。Step6:(邏輯同上)嘗試從
BeanFactory
中加載所有的協議,注意:ServiceBean
的List protocols
是一個集合,也即一個服務可以通過多種協議暴露給消費者。Step7:(邏輯同上)設置
ServiceBean
的path
屬性,path
屬性存放的是dubbo:service
的beanName
(dubbo:service id)。Step8:如果為啟用延遲暴露機制,則調用
export
暴露服務。首先看一下isDelay
的實現,然后重點分析export
的實現原理(服務暴露的整個實現原理)。
ServiceBean#isDelay(),源碼如下:
private boolean isDelay() {
Integer delay = getDelay();
ProviderConfig provider = getProvider();
if (delay == null && provider != null) {
delay = provider.getDelay();
}
return supportedApplicationListener && (delay == null || delay == -1);
}
先從 ServiceConfig
獲取 delay
屬性,如果為 null
,則獲取 ProviderConfig
的 delay
屬性,最后如果還是 null
或配置為 -1
表示延遲暴露服務。可見Dubbo獲取運行參數的層級,便于更精確化的配置各種參數。
通過 supportedApplicationListener
可以猜到服務延遲暴露是通過Spring容器的監聽器觸發的。個人更傾向于明確設置 delay=-1
或者所有層級都不配置,因為如果提早暴露服務,此時其他的Spring bean可能還未初始化完成,而暴露出去的服務大部分情況下依賴于Spring的其他bean來實現業務功能,如果提早接收到客戶端的請求,難免會出現各種異常。
ServiceBean#onApplicationEvent(),源碼如下:
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
如果有設置 dubbo:service
或 dubbo:provider
的屬性 delay
,或配置 delay
為 -1
,都表示啟用延遲機制,單位為毫秒,設置為 -1
,表示等到Spring容器初始化后再暴露服務。
從這里也可以看出,Dubbo暴露服務的處理入口為:ServiceBean#export --> ServiceConfig#export
。
2 ServiceConfig 暴露服務
從前一節代碼分析可知,最后一步是調用 ServiceBean
的父類 ServiceConfig#export
方法暴露服務。
2.1 第一步:ServiceConfig#export 暴露服務
調用鏈:ServiceBean#afterPropertiesSet --> ServiceConfig#export
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && !export) { // @ step1
return;
}
if (delay != null && delay > 0) { // @ step2
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport(); // @ step3
}
}
Step1:判斷是否暴露服務,由
dubbo:service export=“true|false”
來指定。Step2:如果啟用了
delay
機制,如果delay
大于0,表示延遲多少毫秒后暴露服務,使用ScheduledExecutorService
延遲調度,最終調用doExport
方法。Step3:執行具體的暴露邏輯
doExport
,需要大家留意:delay=-1
的處理邏輯( 基于Spring事件機制觸發 )。
2.2 第二步:ServiceConfig#doExport 暴露服務
調用鏈:ServiceBean#afterPropertiesSet --> ServiceConfig#export --> ServiceConfig#doExport
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
checkDefault(); // @ step1
if (provider != null) {
if (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {
registries = provider.getRegistries();
}
if (monitor == null) {
monitor = provider.getMonitor();
}
if (protocols == null) {
protocols = provider.getProtocols();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
if (ref instanceof GenericService) { // @ step2
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);
}
checkInterfaceAndMethods(interfaceClass, methods);
checkRef();
generic = Boolean.FALSE.toString();
}
if (local != null) { // @ step3
if ("true".equals(local)) {
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 implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if (stub != null) { // @ step4
if ("true".equals(stub)) {
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 implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
checkApplication(); // @ step5
checkRegistry();
checkProtocol();
appendProperties(this);
checkStubAndMock(interfaceClass); // @ step6
if (path == null || path.length() == 0) {
path = interfaceName;
}
doExportUrls(); // @ step7
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), ref, interfaceClass); // @ step8
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
private void checkDefault() { // @ step1
if (provider == null) {
provider = new ProviderConfig();
}
appendProperties(provider);
}
Step1:如果
dubbo:servce
標簽也就是ServiceBean
的provider
屬性為空,調用appendProperties
方法,填充默認屬性,其具體加載順序:1. 從系統屬性加載對應參數值,參數鍵:dubbo.provider.屬性名,System.getProperty。 2. 從屬性配置文件加載對應參數值,可通過系統屬性指定屬性配置文件: dubbo.properties.file,如果該值未配置,則默認取 dubbo.properties 屬性配置文件。
Step2:校驗
ref
與interface
屬性。如果ref
是GenericService
,則為Dubbo的泛化實現,然后驗證interface
接口與ref
引用的類型是否一致。Step3:
dubbo:service
local機制,已經廢棄,被stub
屬性所替換。Step4:處理本地存根
Stub
。Step5:校驗
ServiceBean
的application
、registry
、protocol
是否為空,并從系統屬性(優先)、資源文件中填充其屬性。系統屬性、資源文件屬性的配置如下:application dubbo.application.屬性名,例如 dubbo.application.name registry dubbo.registry.屬性名,例如 dubbo.registry.address protocol dubbo.protocol.屬性名,例如 dubbo.protocol.port service dubbo.service.屬性名,例如 dubbo.service.stub
Step6:校驗
stub
、mock
類的合理性,是否是interface
的實現類。Step7:執行
doExportUrls()
方法暴露服務,接下來會重點分析該方法。Step8:將服務提供者信息注冊到
ApplicationModel
實例中。
2.3 第三步:ServiceConfig#doExportUrls 暴露服務
調用鏈:ServiceBean#afterPropertiesSet --> ServiceConfig#export --> ServiceConfig#doExport --> ServiceConfig#doExportUrls
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true); // @ step1
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs); // @ step2
}
}
Step1:首先遍歷
ServiceBean
的List registries
(所有注冊中心的配置信息),然后將地址封裝成URL對象,關于注冊中心的所有配置屬性,最終轉換成url的屬性(?屬性名=屬性值),loadRegistries(true)
,參數的意思:true
,代表服務提供者,false
:代表服務消費者,如果是服務提供者,則檢測注冊中心的配置,如果配置了register=“false”
,則忽略該地址,如果是服務消費者,并配置了subscribe=“false”
則表示不從該注冊中心訂閱服務,故也不返回。Step2:然后遍歷配置的所有協議,根據每個協議,向注冊中心暴露服務,接下來重點分析
doExportUrlsFor1Protocol
方法的實現細節。
所以,從上面代碼,可以看出 Dubbo同一個服務支持多種服務協議、支持向多種注冊中心注冊,很方便同一功能由各種不同實現方式的客戶端調用。