Dubbo是如何搭上Spring的車的?

今天這篇文章,我們主要來聊一聊Dubbo是如何在Spring環境下運作的。

我們使用Dubbo作服務治理,大部分情況下都是使用Spring框架作爲底層框架的。衆所周知,Spring框架是一款十分優秀的框架,其很大的一個亮點就是支持擴展。所以這一篇,我們一起來看一看,Dubbo是如何搭上Spring這輛車的。

1 Dubbo“上車”的時機

沒錯,Dubbo搭車並不是一呼啦就上車的。這其中有幾個關鍵時機,一是配置的讀取,二是服務的解析

1.1 配置讀取

你應該知道,Spring的一個重要特性是支持各種各樣的配置。顯而易見,Dubbo的配置也應當遵循Spring的一些規範。Dubbo要想搭上Spring這輛車,在Spring框架啓動的時候,就得一併將自己的配置也讀取進內存,然後解析出來。

1.2 服務解析

Dubbo服務其實就是我們實現的功能接口,提供出來就成了服務。

其實準確來說,服務也是一種配置。但這裏我將服務單獨拉出來了,因爲服務的解析其實還是與配置的讀取有不同之處的。Dubbo啓動時可能要發佈服務,有可能要引用服務,因此在Spring啓動之時,會有一些其他額外的操作。

服務解析主要分爲以下幾個方面的內容:

  1. 服務配置的讀取
  2. 對於服務端而言,要實現服務的發佈
  3. 對於客戶端而言,要實現服務的引用

針對以上關鍵點,下面我們一一來看。

2. 配置讀取

首先,你可曾想過,配置讀取的“結果”是什麼?

沒錯,是Dubbo配置類! 我們要將XML配置或.properties配置,讀取成配置類。然後配置類加載到內存中,這樣我們就可以在Spring運行時直接使用了。

下面我們一起來看看Dubbo中支持的兩種讀取配置的方式。一是XML方式讀取,二是註解方式讀取。

2.1 XML方式配置讀取

Spring框架支持自定義標籤的配置讀取。Dubbo正是利用了這一點來進行XML配置的讀取。

在Spring框架中,解析配置時,會區分默認標籤元素和自定義標籤元素

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	if (delegate.isDefaultNamespace(root)) {
		NodeList nl = root.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element) {
				Element ele = (Element) node;
				if (delegate.isDefaultNamespace(ele)) {
					// $-- 對默認元素的處理
					parseDefaultElement(ele, delegate);
				}
				else {
					// $-- 對自定義元素的處理
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		// $-- 對自定義元素的處理
		delegate.parseCustomElement(root);
	}
}

默認標籤的解析不再細究,自定義標籤元素的解析如下

public BeanDefinition parseCustomElement(Element ele) {
	// $-- containingBd爲父類bean,對頂層元素的解析應設置爲null
	return parseCustomElement(ele, null);
}

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	// $-- 獲取自定義標籤的命名空間
	String namespaceUri = getNamespaceURI(ele);
	if (namespaceUri == null) {
		return null;
	}
	// $-- 根據命名空間找對應的handler處理器
	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
	if (handler == null) {
		error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
		return null;
	}
	// $-- handler解析
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

首先,獲取該自定義配置的命名空間。如 Dubbo對應的命名空間爲:http://dubbo.apache.org/schema/dubbo
隨後,根據命名空間,找到對應的handler處理器。Dubbo對應的handler爲:DubboNamespaceHandler
最後,利用此handler的parse方法來解析元素的標籤。

這裏插一句,命名空間和handler處理器的映射由Dubbo包內的一個配置文件配置,配置文件路徑爲:/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/spring.handlers,內容爲:

http\://dubbo.apache.org/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

熟悉Spring的人應該知道,Spring啓動的時候會掃描resources下的spring.handlers文件,獲取對應命名空間和相應的handler處理器,從而來解析自定義標籤。

2.1.1 handler處理器的初始化

在獲取到Handler之後,Spring會調用其init方法來初始化。對於Dubbo來說,會調用DubboNamespaceHandler的init方法來註冊BeanDefinitionParser解析器。

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 AnnotationBeanDefinitionParser());
}

整個init方法全是BeanDefinitionParser的註冊,而且除了annotation標籤使用AnnotationBeanDifinition解析,其他標籤都是使用了DubboBeanDefinitionParser標籤來進行解析。

到這裏,一個Dubbo的handler就已經加載進來了。下面我們看一下,handler標籤解析的相關邏輯。

2.1.2 Dubbo元素標籤的解析

通過handler處理器的獲取,我們已經註冊了多種Dubbo標籤的解析器。當Spring解析XML文件,遇到具體的Dubbo標籤時,就會調用其對應的解析器來進行解析。

例如,當Spring解析到Dubbo的application元素解析時,會調用DubboBeanDefinitionParser的parse方法

<dubbo:application name="hservice" owner="hewie"/>
public BeanDefinition parse(Element element, ParserContext parserContext) {
    return parse(element, parserContext, beanClass, required);
}

具體的parse方法邏輯判斷比較多,但大多是相關的賦值操作。我們只講Dubbo與Spring的接入點,感興趣的同學可以自行研究。

2.2 註解方式配置讀取

下面我們一起來看一下Dubbo註解方式配置的讀取。

Dubbo通過註解來引入支持配置前綴(如 dubbo.application、dubbo.registry等,這一點是硬編碼進來的)。然後可以讀取環境變量中的這一類前綴的配置,加載生成對應的配置類到Spring容器中。至於環境變量中的配置,可以在XML中,也可以在.properties文件中,總之可以加載到環境變量中就可以被讀取。

Dubbo中,使用註解進行配置肯定繞不開@EnableDubbo註解。

2.2.1 @EnableDubbo

我們先來看一下@EnableDubbo註解的定義。

Target({ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
Inherited
Documented
EnableDubboConfig
DubboComponentScan
public @interface EnableDubbo {
    @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
    
    @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(annotation = EnableDubboConfig.class, attribute = "multiple")
    boolean multipleConfig() default false;
}

EnableDubbo註解可以配置要掃描的Dubbo配置路徑和是否綁定多個spring bean。
我們可以看到@EnableDubbo主要還是依賴@EnableDubboConfig和@DubboComponentScan這兩個註解。
@DubboComponentScan,顧明思議,主要負責Dubbo配置掃描功能,而@EnableDubboConfig則主要負責解析配置功能。

2.2.2 @DubboComponentScan

讓我們先來看一下@DubboComponentScan註解的定義吧

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

可以看到@DubboComponentScan註解上通過Spring的@Import註解引入了DubboComponentScanRegistrar類。而DubboComponentScanRegistrar實現了ImportBeanDefinitionRegistrar接口,因此會調用registerBeanDefinitions方法來手動註冊Bean到容器中

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	// $-- 獲取要掃描的包路徑
	Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
	// $-- 註冊ServiceAnnotation後置處理器
    registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
	// $-- 註冊ReferenceAnnotation後置處理器
    registerReferenceAnnotationBeanPostProcessor(registry);
}

可以看到,邏輯非常清晰,主要是掃描包路徑,然後註冊兩個後置處理器(這兩個後置處理器在後續服務解析中有用到)。具體的邏輯,由於篇幅有限,此處不再贅述。

2.2.3 @EnableDubboConfig

下面我們來看看@EnableDubboConfig註解吧

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTI
ME)
@Inherited
@Documented
@Import(DubboConfigConfigurationSelector.class)
public @interface EnableDubboConfig {
    boolean multiple() default false;
}

@EnableDubboConfig同樣使用了@Import註解,引入了DubboConfigConfigurationSelector類。
DubboConfigConfigurationSelector類實現了ImportSelector接口,代表其會通過selectImports方法引入外部配置

public String[] selectImports(AnnotationMetadata importingClassMetadata) {

    AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableDubboConfig.class.getName()));

    // $-- 獲取EnableDubboConfig註解的multiple值
    boolean multiple = attributes.getBoolean("multiple");

    if (multiple) {
        return of(DubboConfigConfiguration.Multiple.class.getName());
    } else {
        // $-- 獲取單個配置
        return of(DubboConfigConfiguration.Single.class.getName());
    }
}

private static <T> T[] of(T... values) {
    return values;
}

首先是獲取@EnableDubboConfig配置的multiple值,然後根據配置的不同,走不同的配置獲取路線。

不必太複雜,我們就通過單配置,即DubboConfigConfiguration類的Single靜態內部類,來看一下到底有什麼花樣。

@EnableDubboConfigBindings({
		@EnableDubboConfigBinding(prefix = "dubbo.application", type = ApplicationConfig.class),
        @EnableDubboConfigBinding(prefix = "dubbo.module", type = ModuleConfig.class),
        @EnableDubboConfigBinding(prefix = "dubbo.registry", type = RegistryConfig.class),
        @EnableDubboConfigBinding(prefix = "dubbo.protocol", type = ProtocolConfig.class),
        @EnableDubboConfigBinding(prefix = "dubbo.monitor", type = MonitorConfig.class),
        @EnableDubboConfigBinding(prefix = "dubbo.provider", type = ProviderConfig.class),
        @EnableDubboConfigBinding(prefix = "dubbo.consumer", type = ConsumerConfig.class)
})
public static class Single {
}

可以看到,Single靜態內部類只是一個空殼子,它的實際作用只是@EnableDubboConfigBindings註解的承載器。

那麼這個@EnableDubboConfigBindings又是何方神聖呢?@EnableDubboConfigBindings內部配置了很多的@EnableDubboConfigBinding,這又是幹什麼的呢?我們先來看一下@EnableDubboConfigBindings註解的定義吧

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboConfigBindingsRegistrar.class)
public @interface EnableDubboConfigBindings {
    EnableDubboConfigBinding[] value();
}

@EnableDubboConfigBindings也是通過@Import註解引入了DubboConfigBindingsRegistrar類,DubboConfigBindingsRegistrar實現了ImportBeanDefinitionRegistrar接口,可以通過registerBeanDefinitions方法向Spring容器注入BeanDefinition

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

	AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableDubboConfigBindings.class.getName()));

    // $-- 獲取EnableDubboConfigBindings註解的value值
    AnnotationAttributes[] annotationAttributes = attributes.getAnnotationArray("value");

    DubboConfigBindingRegistrar registrar = new DubboConfigBindingRegistrar();
    registrar.setEnvironment(environment);

    // $-- 將每個EnableDubboConfigBinding註解包含的bean註冊到Spring容器
    for (AnnotationAttributes element : annotationAttributes) {

        registrar.registerBeanDefinitions(element, registry);

    }
}

內部邏先是獲取內部配置的一串串@EnableDubboConfigBinding註解的值,然後將每一個bean註冊到Spring容器中。

so,重點還是要回到內部的@EnableDubboConfigBiding註解,它的作用應當是獲取配置並註冊到Spring容器中。其引入了DubboConfigBindingRegistrar類。它的registerBeanDefinitions方法就是上面註冊Bean到Spring容器的方法。

protected void registerBeanDefinitions(AnnotationAttributes attributes, BeanDefinitionRegistry registry) {

    // $-- 解析要註冊的配置類、配置前綴、是否多配置參數,繼續調用註冊
    String prefix = environment.resolvePlaceholders(attributes.getString("prefix"));

    Class<? extends AbstractConfig> configClass = attributes.getClass("type");

    boolean multiple = attributes.getBoolean("multiple");

    registerDubboConfigBeans(prefix, configClass, multiple, registry);

}

首先是通過註解,獲取配置的配置類、配置前綴、是否多配置,然後調用registerDubboConfigBeans進行註冊處理

 private void registerDubboConfigBeans(String prefix,Class<? extends AbstractConfig> configClass, boolean multiple, BeanDefinitionRegistry registry) {

    // $-- 解析獲取配置屬性,如:dubbo.application.name=333  key=name value=333
    Map<String, Object> properties = getSubProperties(environment.getPropertySources(), prefix);

    if (CollectionUtils.isEmpty(properties)) {
        if (log.isDebugEnabled()) {
            log.debug("There is no property for binding to dubbo config class [" + configClass.getName()
                    + "] within prefix [" + prefix + "]");
        }
        return;
    }

    // $-- 組裝獲取配置對象Config的name
    Set<String> beanNames = multiple ? resolveMultipleBeanNames(properties) :Collections.singleton(resolveSingleBeanName(properties, configClass, registry));

    for (String beanName : beanNames) {

        // $-- 註冊該配置對象DubboConfigBean
        registerDubboConfigBean(beanName, configClass, registry);

        // $-- 註冊該配置對象對應的後置處理器DubboConfigBindingBean
        registerDubboConfigBindingBeanPostProcessor(prefix, beanName, multiple, registry);
    }
}

registerDubboConfigBeans先是獲取配置的屬性值,然後組裝出配置對象的name,最後進行註冊。
註冊也分爲兩部分,一是註冊Dubbo配置Bean,二是註冊該配置Bean對應的後置處理器(DubboConfigBindingBeanPostProcessor)。

具體的註冊流程非常簡單,基本上也只是調用Spring的API,這裏略過了。

經過這樣一段解析,Dubbo就將配置類都已經加載到了Spring容器中。

3. 服務解析

上面介紹了Spring環境下Dubbo配置是如何被讀取,加載到內存的。下面我們簡單來聊聊服務的解析。

Dubbo服務的解析可以分爲兩個部分,即爲服務發佈服務引用。下面我們將圍繞這兩個方面,分別來說明一下服務是如何工作的。

首先,我們要問一個問題,服務是什麼?
所謂服務,通俗地講,就是我們提供給外部的一些接口。

那麼Dubbo中服務是如何承載的呢?
Dubbo在客戶端和服務端分別有不同的Bean來進行承載。在服務端,也就是提供服務的應用中,通過ServiceBean來表示對外提供的服務;在客戶端,也就是服務調用方,通過ReferenceBean來表示外部服務的引用。

那麼客戶端與服務端的服務,分別是什麼時候初始化的呢?通俗一點,服務發佈服務引用是如何完成的呢?這需要我們分別來詳細看一下。

3.1 服務發佈

服務發佈具體是什麼意思呢?簡單來說,就是說我實現了一個功能,要把這個功能作爲一個服務提供給你,得先讓你知道從哪裏獲取服務吧!這就需要發佈服務了。我們一般發佈服務,都是發佈在類似於Zookeeper這樣的註冊中心中。這樣大家都通過註冊中心這個“中間商”,就能獲取到自己想要的服務了。

3.1.1 ServiceBean的創建

首先,服務發佈需要構建我們對外提供的服務,也就是ServiceBean。那麼ServiceBean是如何初始化的呢?這裏也分爲兩種方式,一種是XML方式配置,一種是註解方式配置。

3.1.1.1 XML方式ServiceBean的創建

XML方式配置的服務,樣例如下:

<dubbo:service ref="userService" interface="cn.hewie.hservice.facade.UserService" />

XML方式ServiceBean的創建與上述Dubbo XML配置類型的解析類似,都是在DubboBeanDefinitionParser類中通過parse方法進行解析的,這裏不再贅述。

3.1.1.2 註解方式ServiceBean的創建

註解方式配置的服務,樣例如下:

package cn.hewie.hservice.facade.impl;

import cn.hewie.hservice.facade.UserService;
import com.alibaba.dubbo.config.annotation.Service;


@Service
public class UserServiceImpl implements UserService {

    @Override
    public String getNameById(String id) {
        return id + " append name";
    }
}

可以看到,這個UserServiceImpl似乎跟普通的Spring類沒啥區別。但是要注意到,唯一的不同點在於其@Service註解並非Spring的註解,而是Dubbo包的註解!

註解方式ServiceBean的創建依賴於ServiceAnnotationBeanPostProcessor類。ServiceAnnotationBeanPostProcessor這個類看起來有點眼熟。對,沒錯,@EnableDubbo註解開啓後,會將此ServiceAnnotationBeanPostProcessor註冊到Spring容器中。

ServiceAnnotationBeanPostProcessor類實現了BeanDefinitionRegistryPostProcessor接口。在Spring中,實現了BeanDefinitionRegistryPostProcessor接口的類,在容器啓動激活各種BeanFactory處理器時,會調用其postProcessBeanDefinitionRegistry方法。BeanDefinitionRegistryPostProcessor接口的這種機制可以讓用戶手動註冊Bean到容器中。而ServiceBean註解方式的創建正是通過這種方式來實現的。

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
	// $-- 獲取用戶註解配置的包掃描(包名可能含有佔位符,需要解析)
    Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);

    // $-- 觸發ServiceBean的定義和注入
    if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
        registerServiceBeans(resolvedPackagesToScan, registry);
    } else {
        if (logger.isWarnEnabled()) {
            logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
        }
    }
}

3.1.2 ServiceBean的暴露

在Dubbo中,服務的發佈實際上就是指ServiceBean的暴露。那麼,ServiceBean創建好了,啥時候暴露呢?

在Dubbo中,一個ServiceBean的暴露是有多個時機的。具體來說,可以分爲以下兩類:

  1. 通過afterPropertiesSet發佈
  2. 通過onApplicationEvent事件發佈
3.1.2.1 通過afterPropertiesSet發佈

如果你熟悉Spring的生命週期,那麼你應該瞭解InitializingBean這個接口。
InitializingBean接口的afterPropertiesSet方法是Spring中的一個擴展點,可以很方便的用來定製化Bean的加載。當Spring中的一個Bean進行初始化的時候,會調用其初始化方法,如果其實現了InitailizingBean,則會調用其afterPropertiesSet方法。

Dubbo中的ServiceBean類實現了InitializingBean接口,所以在Spring容器初始化、生成服務Bean的時候,會調用其afterPropertiesSet方法。

public void afterPropertiesSet() throws Exception {
	// 此處省略若干行
    // $-- 如果非延遲暴露,則立即進行服務發佈
    if (!isDelay()) {
        export();
    }
}

private boolean isDelay() {
    Integer delay = getDelay();
    ProviderConfig provider = getProvider();
    if (delay == null && provider != null) {
        delay = provider.getDelay();
    }
    // $-- 默認爲延遲發佈
    return supportedApplicationListener && (delay == null || delay == -1);
}

public void export() {
   super.export();
    // Publish ServiceBeanExportedEvent
    publishExportEvent();
}

afterPropertiesSet方法中會判斷是否爲延遲發佈,如果非延遲發佈,則會調用父類ServiceConfig來進行服務暴露。

具體的暴露邏輯我們後續文章再述。

3.1.2.2 通過onApplicationEvent事件發佈

當Spring啓動完成後,會發布ContextRefreshedEvent事件。Dubbo可以通過監聽此事件,來進行服務的發佈。

ServiceBean中實現了ApplicationListener接口,添加了對ContextRefreshedEvent事件的監聽響應。其中,就包含了服務的暴露。代碼如下

public void onApplicationEvent(ContextRefreshedEvent event) {
	// $-- Spring容器實例化bean完成,發佈ContextRefreshEvent事件,回調此方法
    if (isDelay() && !isExported() && !isUnexported()) {
        if (logger.isInfoEnabled()) {
            logger.info("The service ready on spring started. service: " + getInterface());
        }
        export();
    }
}

public void export() {
    super.export();
    // Publish ServiceBeanExportedEvent
    publishExportEvent();
}
3.1.2.3 兩種服務發佈方式的比較

以上就是Dubbo服務發佈的兩種方式。可以看到,兩種方式發佈服務的時機是不同的。一種是在Spring啓動過程中,當加載到Dubbo的ServiceBean時,就馬上進行服務的發佈。另一種則是比較“延遲”的,是當Spring容器啓動後,才進行發佈的。
Dubbo默認是使用延遲發佈的,也就是Spring容器啓動後發佈。

3.2 服務引用

服務引用具體是什麼意思呢?簡單來說,就是說我實現了一個功能,但是這個功能可能需要調用另一個應用的服務,這就需要服務引用了,即創建一個實際調用服務的引用,這樣我們就可以像調用本地服務一樣調用遠程服務了。

3.2.1 ReferenceBean的創建

首先,服務引用需要拿到一個外部服務的引用,也就是ReferenceBean。那麼ReferenceBean是如何初始化的呢?這裏也分爲兩種方式,一種是XML方式配置,一種是註解方式配置。

3.2.1.1 XML方式ReferenceBean的創建

XML方式配置的服務引用,樣例如下:

<dubbo:reference id="userService" interface="cn.hewie.hservice.facade.UserService" check="false"/>

XML方式ReferenceBean的創建與Dubbo XML配置類型的解析類似,都是在DubboBeanDefinitionParser類中通過parse方法進行解析的,這裏不再贅述。

3.2.1.2 註解方式ReferenceBean的創建

註解方式配置的服務,樣例如下:

package cn.hewie.hweb.integration.impl;

import cn.hewie.hservice.facade.UserService;
import cn.hewie.hweb.integration.AuthService;
import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;

@Service
public class AuthServiceImpl implements AuthService {

    @Reference(check = false)
    private UserService userService;

    @Override
    public String getNameById(String id) {
        return userService.getNameById(id);
    }
}

可以看到,這個AuthServiceImpl含有一個內部的UserService對象,該對象上使用了Dubbo包下的@Reference註解進行修飾,代表這是一個外部服務的引用。

註解方式ReferenceBean的創建依賴於ReferenceAnnotationBeanPostProcessor類。沒錯,ReferenceAnnotationBeanPostProcessor這個類也是@EnableDubbo註解解析時引入的,具體可以看@EnableDubbo註解的引入邏輯。

ReferenceAnnotationBeanPostProcessor類繼承了AnnotationInjectedBeanPostProcessor類,而AnnotationInjectedBeanPostProcessor類實現了InstantiationAwareBeanPostProcessor接口。在Spring中,實現了InstantiationAwareBeanPostProcessor接口的類,會在該Bean創建、填充屬性時,通過postProcessPropertyValues方法來實現所需屬性的獲取。

以下是AnnotationInjectedBeanPostProcessor類postProcessPropertyValues方法。其方法調用過程中,會創建ReferenceBean。這一段的代碼執行涉及多個包,中間也經歷過多次改動,此處不再贅述,感興趣的小夥伴可以自行debug嘗試。

public PropertyValues postProcessPropertyValues(
    PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {

    InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
    try {
        metadata.inject(bean, beanName, pvs);
    } catch (BeanCreationException ex) {
        throw ex;
    } catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getName()
                + " dependencies is failed", ex);
    }
    return pvs;
}

3.2.2 ReferenceBean的引用

Dubbo中,一個ReferenceBean代表一個服務引用。通過引用服務的時機不同,可以分爲以下兩類:

  1. 通過getObject方法引用
  2. 通過afterPropertiesSet引用
  3. 註解式的引用
3.2.2.1 通過getObject引用

FactoryBean是Spring中的一個特殊的Bean,凡是實現了FactoryBean的類,都會在Bean加載的時候,調用其getObject方法生成Bean,然後註冊到容器中。

Dubbo中的ReferenceBean繼承了Spring的FactoryBean,因此可以通過FactoryBean的這種特性,從而實現外部服務的引用。

public Object getObject() throws Exception {
    return get();
}

可以看到,ReferenceBean的getObject方法非常簡單,就是調用了ReferenceConfig的get方法。

public synchronized T get() {
   if (destroyed) {
        throw new IllegalStateException("Already destroyed!");
    }
    if (ref == null) {
        init();
    }
    return ref;
}

ReferenceConfig的get方法邏輯也很簡單,最主要的加載邏輯是在init方法中。init方法會生成外部調用服務代理並返回,註冊到Spring容器中。

init方法是服務引用過程的主要方法,裏面具體的邏輯比較多,我們在後面的文章中會專門敘述。

3.2.2.2 通過afterPropertiesSet引用

ReferenceBean也實現了InitializingBean接口,因此也可以利用Spring的這個擴展點來進行服務的引用。

public void afterPropertiesSet() throws Exception {
    // 此處省略若干行
    Boolean b = isInit();
    if (b == null && getConsumer() != null) {
        b = getConsumer().isInit();
    }
    if (b != null && b.booleanValue()) {
        getObject();
    }
}

如果配置了需要初始化的屬性,則可以通過調用getObject方法來初始化服務引用。這裏的getObject方法就是上面提到的FactoryBean的getObject方法,只是相當於又多了一個入口。

3.2.2.3 註解式的引用

與ServiceBean的暴露不同,ReferenceBean的發佈還有一種註解式的引用方式。
具體來說,如果是通過註解進行服務的引用配置的話,則會通過註解解析流程來完成服務引用過程。

在上面的ReferenceBean創建過程中,我們聊到了ReferenceAnnotationBeanPostProcessor這個類。通過其繼承自AnnotationInjectedBeanPostProcessor類的postProcessPropertyValues方法,不僅能夠創建ReferenceBean,還能進行服務注入。具體的時機也是有兩個:

  1. 創建了ReferenceBean後,會進行Bean的配置,調用ReferenceBeanBuilder#postConfigureBean方法後,會調用ReferenceBean的afterProperties方法
  2. 創建了ReferenceBean後,會創建其代理。在代理創建時,會調用ReferenceBeanInvocationHandler的init方法,init方法實際調用的就是ReferenceBean的get方法

經歷了上述的註解解析過程後,整個ReferenceBean就已經完成了服務的發佈了。

3.2.2.3 三種服務引用方式的比較

無論上面哪種方式,都可以實現服務的引用。用戶可以通過配置,走afterProperties來初始化引用;也可以什麼都不管,等待FactoryBean的getObject方法來自動創建引用;亦或者通過註解解析的“一條龍”服務,完成ReferenceBean的創建和發佈。

你可能會問,服務的引用應當有且只有一次創建的過程,這個該如何控制?
當然,創建一次這個限制已經在具體的實現邏輯裏控制了(通過一個initialized變量控制),具體的邏輯,我們會繼續在後面的文章中探討。所謂殊途同歸,無論哪種方式,最終的結果都是完成服務的引用。

4. 總結

總的來說吧,正是由於Spring開放的擴展設計,從而使得Dubbo可以很方便的“搭車”。也正是由此,Spring才能如此廣泛的被應用,而Dubbo,也能通過“搭車”,被更便捷的推廣使用。
相信瞭解了Dubbo“搭車”的過程後,我們能夠更好的瞭解Dubbo的運行機制,從而爲我們的開發、問題的排查助力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章