今年Dubbo發(fā)布了全新的3.0版本,開始面向云原生,有很多細(xì)節(jié)與之前較多使用的2.7.x版本有所不同,本文主要介紹Dubbo3.0下的服務(wù)引用流程與之前的不同點,一些相同的邏輯就簡單介紹下。
何時觸發(fā)服務(wù)引用
Dubbo2.7.x版本觸發(fā)服務(wù)引用的地方有兩個,一個是因為 ReferenceBean 實現(xiàn)了 InitializingBean 接口,在 Spring 容器調(diào)用 afterPropertiesSet 方法時進行服務(wù)引用,使用該方式可通過init屬性開啟,在最后調(diào)用 getObject 方法;一個是對用的服務(wù)在其他類中被引用時,直接調(diào)用 getObject 方法。Dubbo默認(rèn)使用后者方式。
Dubbo3.0中進行了一些改動。
Dubbo3.0同樣繼承了 InitializingBean 接口,但是實現(xiàn)邏輯發(fā)生了變化,在 afterPropertiesSet 方法中首先通過 BeanFactory 獲取 BeanDefinition ,通過 BeanDefinition 獲取 interfaceClass 和 interfaceName 等屬性值,解析 xml 或者注解形式下的一些屬性值。最后獲取 ReferenceBeanManager Bean對象,將該 referenceBean 添加到一個 變量名為 referenceConfigMap 的 ConcurrentHashMap 中,并映射 reference key 到 referenceBeanName。
以上是 afterPropertiesSet 方法中的主要邏輯,我們可以發(fā)現(xiàn)與2.7.x版本有所不同,3.0版本放棄在該方法中進行服務(wù)引用,只提供在 getObject方法中進行懶加載,只創(chuàng)建一個lazy Proxy, 當(dāng)首次調(diào)用代理時才會真正的創(chuàng)建,而
Dubbo2.7.x在 getObject 方法中會直接調(diào)用 ReferenceConfig#createProxt 方法,而 Dubbo3.0 會只創(chuàng)建一個lazy Proxy, 并創(chuàng)建ReferenceConfig,在首次調(diào)用代理的時候再創(chuàng)建createProxy。
為什么現(xiàn)在只保留懶加載式的方式呢?以下是官方文檔給出的解釋。
當(dāng)Spring通過類型搜索Bean時,如果Spring無法確定一個 factory bean 的類型時,它可能會嘗試對其進行初始化。而 ReferenceBean 也是一個 FactoryBean。(但是該問題在Dubo中已經(jīng)通過裝飾BeanDefinition解決了)另外,如果一些 ReferenceBeans 依賴很早初始化的bean, 而 dubbo config benas 還沒有準(zhǔn)備好,如果馬上初始化 dubbo reference,可能會出現(xiàn)一些意想不到的問題。所以,在Dubbo3.0中,referencebean 初始化時,只會創(chuàng)建一個 lazy proxy,與dubbo 引用相關(guān)的資源不會被初始化。這樣就排除了Spring的影響,dubbo配置初始化是可控的了。
Dubbo3.0是如何創(chuàng)建一個 lazy proxy for reference?
答案就在 ReferenceBean#createLazyProxy 方法中。
private void createLazyProxy() {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTargetSource(new DubboReferenceLazyInitTargetSource());
proxyFactory.addInterface(interfaceClass);
....
....
this.lazyProxy = proxyFactory.getProxy(this.beanClassLoader);
}
private class DubboReferenceLazyInitTargetSource extends AbstractLazyCreationTargetSource {
@Override
protected Object createObject() throws Exception {
return getCallProxy();
}
@Override
public synchronized Class<?> getTargetClass() {
return getInterfaceClass();
}
}
玄機就在 AbstractLazyCreationTargetSource 這個抽象類中,對于里面的 createObject 方法,該抽象類要求子類調(diào)用這個方法來返回延遲初始化的對象,第一次調(diào)用代理時調(diào)用該方法。當(dāng)您需要將某個依賴項的引用傳遞給對象但您實際上不希望在首次使用該依賴項之前創(chuàng)建該依賴項時,這很有用。 一個典型的場景是連接到遠程資源。這正好解決上述Dubbo3.0中的訴求。
當(dāng)首次調(diào)用代理對象時,最終會調(diào)用 getCallProxy 方法。
private Object getCallProxy() throws Exception {
if (referenceConfig == null) {
throw new IllegalStateException("ReferenceBean is not ready yet, please make sure to call reference interface method after dubbo is started.");
}
return referenceConfig.get();
}
這時我們發(fā)現(xiàn)用到了 ReferenceConfig 類,該類是服務(wù)引用的關(guān)鍵類,那它是在什么時候被創(chuàng)建的呢?
在 ReferenceBean 中,只有一個方法對 ReferenceConfig 進行了賦值。
public void setKeyAndReferenceConfig(String key, ReferenceConfig referenceConfig) {
this.key = key;
this.referenceConfig = referenceConfig;
}
繼續(xù)追蹤,在 ReferenceBeanManager#initReferenceBean中調(diào)用了該方法,該類是不是很熟悉,就是在上述的 afterPropertiesSet 方法提到了該類,將 ReferenceBean 注冊到 ReferenceBeanManager 中。
最終我們發(fā)現(xiàn) DubboConfigBeanInitializer 類 中的 afterPropertiesSet 方法 調(diào)用了上述邏輯。
這時我們可能有疑惑,ReferenceBean 和 DubboConfigBeanInitializer 都實現(xiàn)了 InitializingBean 接口,加載的先后順序不同會不會導(dǎo)致問題的產(chǎn)生。
ReferenceBean#afterPropertiesSet 方法在最后調(diào)用 ReferenceBeanManager#addReference 方法,將 ReferenceBean 注冊到 一個Map 中,在最后有一個判斷,如果 initialized 變量為 true, 則會調(diào)用 initReferenceBean 方法, 這個方法在后面會講到。
referenceBeanMap.put(referenceBeanName, referenceBean);
//save cache, map reference key to referenceBeanName
this.registerReferenceKeyAndBeanName(referenceKey, referenceBeanName);
// if add reference after prepareReferenceBeans(), should init it immediately.
if (initialized) {
initReferenceBean(referenceBean);
}
DubboConfigBeanInitializer
在 DubboConfigBeanInitializer 中的注釋中,提示到 Dubbo Config Bean 必須在注冊所有 BeanPostProcessors 之后初始化,也就是在 AbstractApplicationContext#registerBeanPostProcessors 方法之后。
DubboConfigBeanInitializer 實現(xiàn)了 BeanFactoryAware 和 InitializingBean 接口,
其中 BeanFactoryAware 接口提供了一個方法setBeanFactory(BeanFactory beanFactory),通過該方法我們可以知道實現(xiàn)這個接口的bean屬于哪個beanFactory。
另一個重要的實現(xiàn)方法是 afterPropertiesSet 方法,里面直接調(diào)用init方法。
1)通過 setBeanFactory 方法傳入的beanFactory參數(shù)變量調(diào)用 getBean 方法獲取 ReferenceBeanManager 實例,
2)在被@Reference注解的bean加載前確保一些Dubbo的配置Bean被初始化,并添加到 configManager 中,包括ApplicationConfig,RegisteryConfig,ProviderCOnfig和ConsumerCOnfig類等。初始化的方法很簡單,直接調(diào)用beanFactory.getBean方法。
private void loadConfigBeansOfType(Class<? extends AbstractConfig> configClass, AbstractConfigManager configManager) {
String[] beanNames = beanFactory.getBeanNamesForType(configClass, true, false);
for (String beanName : beanNames) {
AbstractConfig configBean = beanFactory.getBean(beanName, configClass);
configManager.addConfig(configBean);
}
}
- 最后調(diào)用
ReferenceBeanManager#prepareReferenceBeans方法, 在這里調(diào)用了上述的 initReferenceBean 方法,創(chuàng)建 ReferenceConfig,將其添加到 referenceConfigMap 中。
public void prepareReferenceBeans() throws Exception {
initialized = true;
for (ReferenceBean referenceBean : getReferences()) {
initReferenceBean(referenceBean);
}
在 DubboConfigBeanInitializer 和 ReferenceBean 中 都調(diào)用了 initReferenceBean 方法 進行創(chuàng)建 ReferenceConfig, 為什么要在兩處呢,在 ReferenceBean 中創(chuàng)建不是更方便呢?
這是因為在 創(chuàng)建 ReferenceConfig 時需要 Dubbo Config Bean 信息,在 initReferenceBean 方法中也注明了該方法應(yīng)該只在所有dubbo config bean和所有屬性解析器加載后調(diào)用。
在 DubboConfigBeanInitializer 中會加載這些信息,并將 initialized 變量設(shè)置為 true, 所以在 ReferenceBean 中會有個判斷,如果 initialized 為 ture, 就可以直接調(diào)用 initReferenceBean 方法了。