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 请求的方式填充参数,指定 @Adaptivevalue 数组的匹配关系

例子:

## 声明配置
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
}
  • @Adaptivevalue 字段不为空,则实现类查找顺序为 @Adaptive 注解的 value 数组中的,如果都找不到,才找 @SPIvalue
  • @Adaptivevalue 字段为空,则实现类查找顺序为 user.service 参数,找不到才去 @SPIvalue
  • 若查找不到所需的实现类则会抛出异常

进阶使用——拓展点的子类激活

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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值