Spring Cloud Alibaba 教程 | Dubbo(二):简述Dubbo SPI

前言

从今天开始我将分多篇文章源码解析Dubbo框架的实现细节,通过阅读这些文章读者朋友可以深入了解到Dubbo框架,同时还可以达到能够对框架进行按需定制、优化的能力。

在阅读这些文章之前建议读者朋友们先将dubbo源码下载下来,源码可以到github上去下载下来:https://github.com/apache/dubbo
此外我们源码解析是基于Dubbo的2.6.x版本。

Dubbo SPI

SPI全称是Service Provider Interface。起初目的在于提供给厂商做插件开发的,关于Java SPI的实现可以通过这篇文章了解。
Java中的SPI机制:https://blog.csdn.net/u010739551/article/details/100778378

对Dubbo框架有深入了解的朋友们应该知道,Dubbo框架具备有良好的扩展性,而良好的扩展性与使用到的设计模式和Dubbo SPI加载机制是密不可分的。
Dubbo并没有直接使用Java自带的SPI机制,而是扩展了Java SPI,同时兼容Java SPI。Dubbo之所以没有直接复用Java SPI主要是考虑着三个方面:
1、Java SPI会一次性加载所有的实现类,资源浪费,导致性能较差,而Dubbo SPI只有在使用到的时候才会加载,并且将实例对象缓存起来,后面再使用直接从缓存中获取,提高了加载性能。
2、Java SPI的错误处理不是很好,Dubbo SPI优化了错误处理。
3、Dubbo SPI支持IOC和AOP功能,这是Java SPI没有的,后面讲详细介绍实现细节。

对于Dubbo框架来说良好的扩展性保证用户可以根据需求将接口切换成不同的实现类对象,从而达到按需定制的目的。Dubbo将这些可以定制的接口称为扩展接口,这些接口都包含了注解@SPI

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    /**
     * default extension name
     */
    String value() default "";

}
@SPI("dubbo")
public interface Protocol {
    int getDefaultPort();

被@SPI注解标识的接口将具备扩展性,注解的value值表示该扩展接口的默认实现,下面我们来使用一下Dubbo SPI。

第一步:定义一个扩展接口PrintService

package com.alibaba.dubbo.demo.spi;

import com.alibaba.dubbo.common.extension.SPI;

@SPI("printService1")
public interface PrintService {
    public void printInfo();
}

第二步: 定义一个实现类PrintServiceImpl1

import com.alibaba.dubbo.demo.spi.PrintService;

public class PrintServiceImpl1 implements PrintService {

    public PrintServiceImpl1() {
        System.out.println("PrintServiceImpl1 construct");
    }

    @Override
    public void printInfo() {
        System.out.println("=====PrintServiceImpl1=====");
    }

}

第三步: 定义一个名称为全类名扩展接口PrintService的文件,并且文件放置在META-INF/dubbo/internal目录下
在这里插入图片描述
内容是key=value的形式,多个使用换行符分隔。每一个扩展实现类都有一个对应的名称,通过名称获取到指定的实现类对象,这点是和Java SPI不同的。

第四步: 测试Dubbo SPI功能

public class Test {

    public static void main(String[] args) {

        PrintService printService =
               ExtensionLoader.getExtensionLoader(PrintService.class)
                        .getExtension("printService1");
        printService.printInfo();
    }

}

执行结果:

PrintServiceImpl1 construct
=====PrintServiceImpl1=====

扩展类的种类

dubbo框架将扩展接口的实现类分为三种类型:

  1. 普通扩展类
  2. 包装扩展类
  3. 自适应扩展类

普通扩展类

上面举的例子PrintServiceImpl1就是一个普通的扩展类,这种扩展类直接实现了扩展接口,并且在扩展配置文件中配置了对应的key=value。我们可以直接通过ExtensionLoader.getExtensionLoader(XXX.class).getExtension(“key”)就可以获取到实例对象。

包装扩展类

当一个实现类实现了扩展接口的同时,该扩展接口又作为其构造方法参数传入时,那么这样的实现类就称为包装扩展类。这种扩展类在Dubbo框架中通常会以XXXWrapper的形式命名,这种包装扩展类的目的就是为了实现对spi功能的增强,增加AOP控制特性。熟悉Dubbo框架和设计模式的朋友应该知道这种包装扩展类实际上就是装饰器模式。
下面的PrintServiceWrapper就是包装扩展类,同样也必须配置在扩展配置文件中。

public class PrintServiceWrapper implements PrintService {

    private PrintService printService;

    public PrintServiceWrapper(PrintService printService) {
        System.out.println("PrintServiceWrapper construct");
        this.printService = printService;
    }

    @Override
    public void printInfo() {
        System.out.println("printInfo before Wrapper");
        this.printService.printInfo();
        System.out.println("printInfo after Wrapper");
    }
}
printService1=com.alibaba.dubbo.demo.spi.impl.PrintServiceImpl1
printServiceWrapper=com.alibaba.dubbo.demo.spi.impl.PrintServiceWrapper

当一个扩展接口包含了包装扩展类的时候,在获取普通扩展类的时候实际上是获取到了包装扩展类实例对象。加上了上面的包装扩展类PrintServiceWrapper之后再次执行上面的例子结果就会出现AOP的控制特性。

PrintServiceImpl1 construct
PrintServiceWrapper construct
printInfo before Wrapper
=====PrintServiceImpl1=====
printInfo after Wrapper

后面我们会详细介绍这块的实现细节。

自适应扩展类

当一个扩展接口有多种实现类的时候,我们希望不要在配置中写死到底使用哪个指定的扩展类,而是在运行中通过参数URL来动态指定使用的扩展类。这种自适应扩展类分为两种使用方式:

第一种方式:在普通扩展类基础上加上一个@Adaptive注解

import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.demo.spi.PrintService;

@Adaptive
public class PrintServiceImpl2 implements PrintService {

    public PrintServiceImpl2() {
        System.out.println("PrintServiceImpl2 construct");
    }

    @Override
    public void printInfo() {
        System.out.println("=====PrintServiceImpl2=====");
    }
    
}

这种自适应扩展类通过ExtensionLoader.getExtensionLoader(XXX.class).getAdaptiveExtension()获取,并且一个扩展接口最多只有一个自适应扩展类。

第二种方式:在扩展接口的方法上使用@Adaptive注解,这种使用方式实际上并没有产生一个新的实现类,而是通过方法的URL参数动态指定使用的扩展实现类,这种扩展类是普通扩展类。

例如框架中的Transporter扩展接口:

@SPI("netty")
public interface Transporter {

    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;

}

关于这部分内容后面后面会同样会详细讲述。

获取扩展接口实例对象的方式

Dubbo框架中一个扩展接口对应着一个ExtensionLoader实例对象,通过ExtensionLoader实例对象的方法可以获取到扩展接口实例对象,通常有以下三种方式:

方式一:extensionLoader.getAdaptiveExtension()
通过这种方式可以获取到自适应扩展类实例对象,这种自适应扩展类最多只有一个。

方式二:extensionLoader.getDefaultExtension()
当有多个普通扩展实现类时,可以在@SPI注解的value指定默认使用的扩展类,然后通过上面的方式就可以获取到扩展类实例对象。

方式三:extensionLoader.getExtension(“printService1”)
这种方式是通过name名称指定获取的普通扩展类实例对象。name的值就是扩展配置文件中key=value中的key值。

关注公众号了解更多原创博文

Alt

发布了122 篇原创文章 · 获赞 127 · 访问量 93万+

猜你喜欢

转载自blog.csdn.net/u010739551/article/details/104167885