Dubbo之@SPI

迈不过友情╰ 2022-12-05 01:38 309阅读 0赞

学习Apache Dubbo,从官网开始。

关键词:Dubbo RPC SPI


Dubbo的jar包分为两种,一种是org.apache包下的,一种是com.alibaba包下的。本文及后续的使用均来自com.alibaba包下的Dubbo。


█ 如何使用

①创建MAVEN项目,并引入com.alibaba.dubbo依赖。

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>dubbo</artifactId>
  4. <version>2.6.9</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.logging.log4j</groupId>
  8. <artifactId>log4j-core</artifactId>
  9. <version>2.13.3</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.slf4j</groupId>
  13. <artifactId>slf4j-log4j12</artifactId>
  14. <version>1.7.30</version>
  15. </dependency>

②在resources根路径下创建log4j.properties。

  1. log4j.rootLogger=INFO,CONSOLE
  2. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
  3. log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
  4. log4j.appender.CONSOLE.layout.ConversionPattern=[frame] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n

③创建接口,并在接口上标注@SPI注解。

  1. package study.rui.dubbo;
  2. import com.alibaba.dubbo.common.extension.SPI;
  3. @SPI("dog")
  4. public interface AnimalService {
  5. void say();
  6. }

④创建接口实现类。

  1. package study.rui.dubbo.impl;
  2. import study.rui.dubbo.AnimalService;
  3. public class DogService implements AnimalService {
  4. @Override
  5. public void say() {
  6. System.out.println("dog say woo woo");
  7. }
  8. }

⑤在resources下创建META-INF/dubbo文件夹,在文件夹下创建文件,文件名是接口的全限定名,内容和property配置文件类似,也是key=value格式。value是接口实现类的全限定名,key是给这个类起的别名,符合命名规范即可。如:dog=study.rui.dubbo.impl.DogService。(称呼约定,后面内容我将等于号左边叫做配置key,等于号右边叫做配置value。)

20200908200301496.png

⑥运行代码。

  1. import com.alibaba.dubbo.common.extension.ExtensionLoader;
  2. import study.rui.dubbo.AnimalService;
  3. public class SPITest {
  4. public static void main(String[] args) {
  5. ExtensionLoader<AnimalService> loader = ExtensionLoader.getExtensionLoader(AnimalService.class);
  6. // 创建DogService实例
  7. AnimalService animalService = loader.getExtension("dog");
  8. // 结果输出:dog say woo woo
  9. animalService.say();
  10. }
  11. }

总结说明

①学习Dubbo的SPI之前,可以先了解一下Java的SPI,主要的类是:ServiceLoader。对于ServiceLoader的扩展,在很多框架中都能看见它的影子,比如Spring Boot用它实现了自动注入。

②关系梳理图。watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUxODI3MQ_size_16_color_FFFFFF_t_70

③接口的实现类必须提供一个public或protected的无参构造函数。因为Dubbo在创建实例的时候,使用的是:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUxODI3MQ_size_16_color_FFFFFF_t_70 1

④@SPI,只能作用于接口上。这取决于要获取接口的扩展实现类,需要先获取接口的ExtensionLoader实例。ExtensionLoader的构造器被私有化,正常无法通过new来创建实例,(当然如果通过反射获取,那我就告你非礼),可以通过对外暴露的静态方法getExtensionLoader来获取ExtensionLoader实例。获取指定类型的扩展加载器有两个必要条件:类型必须是接口、接口上必须标注@SPI注解。(见源码)

  1. public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
  2. ......
  3. if (!type.isInterface()) {
  4. // 检查参数是否是接口类型,不是,抛异常
  5. throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
  6. } else if (!withExtensionAnnotation(type)) {
  7. // 检查接口上是否标注@SPI,没有,抛异常
  8. throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
  9. }
  10. ......
  11. }

⑤【一夫一妻制】@SPI标注的接口和ExtensionLoader实例是一对一的关系。一个SPI接口只有一个属于它的ExtensionLoader实例,并且一个ExtensionLoader实例只能被一个接口拥有,对应的关系被缓存在EXTENSION_LOADERS中(结婚证)。设计成一夫一妻制解决什么问题呢。比如,例子中dog对应了AnimalService的实现类DogService,那又来了个CarService接口,人家也想起名dog来对应哈弗大狗这种车(HAVADogService)。如果AnimalService和CarService共享一个ExtensionLoader实例,那通过getExtensionLoader(“dog”),到底给你返回DogService还是HAVADogService实例呢。(好好想想我们为什么要实行一夫一妻制,你品,你细品)

⑥@SPI注解有个value属性,用于指定当前接口的默认扩展实现类。value的值就是步骤3中配置文件中的配置key。如果没有设置value的值,则调用如下两个get方法,会返回null。因为我在步骤1中指定了value的值为dog,所有这里会返回DogService实例。

  1. ExtensionLoader<AnimalService> loader = ExtensionLoader.getExtensionLoader(AnimalService.class);
  2. AnimalService service1 = loader.getExtension("true");
  3. AnimalService service2 = loader.getDefaultExtension();
  4. service1.say();
  5. service2.say();

⑦【吕子乔也叫吕小布】人有大名和小名,甚至还有外号。扩展实现类当然也可以同时拥有好几个名称。在步骤3中,将dog=study.rui.dubbo.impl.DogService改成dog,gou=study.rui.dubbo.impl.DogService。等号左边配置多个名称时,用英文逗号分开,即一个接口实现类对应多个配置key。这样通过getExtension(“dog”)或getExtension(“gou”)都能获取到DogService实例。但不管名称有多少个,人都是同一个人,所以这里获取到的都是单例实例。因为ExtensionLoader是将Class对象和实现类实例缓存在EXTENSION_INSTANCES中的。

⑧【狡兔三窟】步骤3中创建的配置文件。不仅仅可以放在META-INF/dubbo路径下,还可以放在META-INF/dubbo/internal和META-INF/services。Dubbo会从这三个地址去加载指定接口全限定名(getExtensionLoader方法的参数)对应的文件内容。

⑨除了通过ExtensionLoader提供的方法获取扩展实现类实例,还可以通过ExtensionFactory的getExtension方法去获取。ExtensionFactory是一个接口,Dubbo为我们提供了三个实现类,分别是AdaptiveExtensionFactory、SpiExtensionFactory和SpringExtensionFactory。AdaptiveExtensionFactory是基础的ExtensionFactory,可通过其获取到其他两个ExtensionFactory。ExtensionLoader的私有构造函数中有这么一段代码:

  1. // 当参数的type类型不是ExtensionFactory类型,通过getAdaptiveExtension()
  2. // 获取到的就是AdaptiveExtensionFactory。这里涉及到了@Adaptive知识点了,将在下一节讲解
  3. this.objectFactory = type == ExtensionFactory.class ? null : (ExtensionFactory)getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();

其中objectFactory是ExtensionFactory,其在依赖注入的方法中被使用到。关于Dubbo提供的依赖注入,将在后面的文章说明。

发表评论

表情:
评论列表 (有 0 条评论,309人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Dubbo SPI

    前言 前面已经讲过[JDK SPI][], dubbo SPI主要基于JDK SPI机制,进行了增强,实现可扩展 为什么不直接用JDK的SPI呢,主要有哪些改变