天天看点

Dubbo SPI使用

为什么

Dubbo

要自己实现一套自己的

SPI

机制呢?
  • Java SPI

    不支持依赖注入,对扩展点的依赖不友好。

    Dubbo SPI

    支持依赖注入,即在实例化扩展点的过程中,通过反射调用扩展点的

    setXXX

    方法,注入依赖的扩展点;
  • Java SPI

    获取实现类方式单一,只能通过遍历获取。

    Dubbo SPI

    支持通过

    key

    获取实现类,使用起来更方便、更灵活;
  • Dubbo SPI

    还实现了强大的自适应扩展和自动激活功能,通过这两个功能可以实现在运行时替换具体实现类(运行到具体的方法时才决定使用哪个实现)以及简化配置;

基本使用

使用步骤
  • jar

    包的

    META-INF/dubbo

    下面创建一个接口全限定名的文件;
  • 接口全限定名文件中以键值对的形式填写实现类;
    • 键可以是任意唯一的标识符,值要求是接口实现类的全限定类名;
  • 接口添加

    @SPI

    注解;
  • 使用

    ExtensionLoader.getExtensionLoader(class)

    获取

    ExtensionLoader

    对象;
  • 使用

    ExtensionLoader::getExtension(key)

    获取接口实现类对象;
例子

META-INF/dubbo

下新建一个

UserService

接口全限定名称的文件。

## 声明配置
key1=com.example.impl.UserServiceImpl
key2=com.example.impl.UserServiceImpl0
           

给接口添加

@SPI

注解。

/**
 * 协议接口
 */
@SPI
public interface UserService {

    String getHello();
}

// 对应实现com.example.impl.UserServiceImpl和com.example.impl.UserServiceImpl0
           

代码使用实例

public static void main(String[] args) {
    //获取ExtensionLoader对象
    ExtensionLoader<UserService> extensionLoader = ExtensionLoader
            .getExtensionLoader(UserService.class);
    UserService adaptiveExtension = extensionLoader.getExtension("key1");
    // 调用的是UserServiceImpl0
    adaptiveExtension.getHello();
}
           
进阶使用——自适应拓展

Dubbo

使用一个

ExtensionLoader

去加载多个接口/抽象类的实现,在不同场景下,同个方法可能需要调用不同的子类实现,所以

Dubbo

提供了一种动态调用子类实现的方式。

注意点:

  • @Adaptive

    注解:用来标注子类、接口方法;
    • 标注子类,表示该子类用作该该接口自适应调用的默认实现;
    • 标注接口方法,表示该接口方法作为自适应实现;
  • @SPI

    注解:它用来某个接口作为

    SPI

    使用,并且

    value

    是自适应方法调用的默认实现;
  • 要求作为自适应调用的方法,必须有一个参数是

    URL

    类型,它以

    GET

    请求的方式填充参数,指定

    @Adaptive

    value

    数组的匹配关系;

例子:

## 声明配置
key1=com.example.impl.UserServiceImpl
key2=com.example.impl.UserServiceImpl0
           
@SPI("key1") // 声明为自适应方法默认key1为key的子类默认实现
public interface UserService {
    
    @Adaptive({"key2", "key1"}) // 声明为自适应方法
    String getHello(User user, URL url, String a);

}

public class UserServiceImpl implements UserService {
    @Override
    public String getHello(User user, URL url, String a) {
        System.out.println("UserServiceImpl");
        return "UserServiceImpl";
    }
}

public class UserServiceImpl0 implements UserService {
    @Override
    public String getHello(User user, URL url, String a) {
        System.out.println("UserServiceImpl0");
        return "UserServiceImpl0";
    }
}

// 调用
public static void main(String[] args) throws IOException {
    // 获取自适应代理类
    UserService adaptiveExtension = ExtensionLoader
            .getExtensionLoader(UserService.class)
            .getAdaptiveExtension();
    // 其实URL任意都行,只要是合法的URL就可以
    adaptiveExtension.getHello(new User(), URL.valueOf("http://127.0.0.1:1000/1?key1=key1&key2=key2"), "11");
    // 最终调用的是key2=UserServiceImpl0
}
           
  • @Adaptive

    value

    字段不为空,则实现类查找顺序为

    @Adaptive

    注解的

    value

    数组中的,如果都找不到,才找

    @SPI

    value

  • @Adaptive

    value

    字段为空,则实现类查找顺序为

    user.service

    参数,找不到才去

    @SPI

    value

  • 若查找不到所需的实现类则会抛出异常;
进阶使用——拓展点的子类激活

SPI

机制正常情况迭代完后,可以获取所有的实现子类,但是有些场景下,我们只需要激活部分子类实现即可,而不需要激活全部的子类,即通过一定的条件去过滤掉不满足条件的子类,返回匹配的子类集合。

  • @Activate

    注解是实现配置化激活的关键,用来标注接口的实现类,在满足场景的情况下才加入调用的返回结果集;
  • 通过

    ExtensionLoader::getActivateExtension

    方法进行调用获取满足条件的实现类实例;
    • 支持的方式有分组 (用的多),指定

      value

      关键字 以及排除法;

以下为

@Activate

的定义:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    // 指定分组, 当ExtensionLoader指定的分组中存在时就返回
    String[] group() default {};
    
    // 指定关键字, 当ExtensionLoader指定的关键字中存在时就返回
    String[] value() default {};
    
    // 满足条件返回时,要求在指定key值的子类实例前面(排序)
    String[] before() default {};
    
    // 满足条件返回时,要求在指定key值的子类实例后面(排序)
    String[] after() default {};
    
    // 满足条件返回时, 进行的排序使用的依据
    int order() default 0;
}