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的运行机制,从而为我们的开发、问题的排查助力。

猜你喜欢

转载自blog.csdn.net/somehow1002/article/details/106911933