dubbo之@Adaptive注解分析

写在前面

dubbo提供了SPI机制可以通过外部配置文件来动态加载扩展类,有时我们可能需要配置这些扩展类挨个执行来满足业务场景,进行一些数据的处理等,此时这些扩展类我们都是需要的,还有一些其他的场景,需要根据外部环境的不同(如某参数的值),来动态的选择使用哪个扩展类,针对这种需求,dubbo提供了@Adaptive注解来完成该功能,源码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD}) // 在类和方法上使用
public @interface Adaptive {
    /**
     设置注入哪个扩展类。目标扩展类名称通过在URL中传递的参数值确定,而URL中参数的key是什么就是通过该方法的返回值确定的,可以有多个,默认值是接口简单名称转
     点分形式,如MyInterface就是my.interface。如果是在URL上没有设置目标扩展类名称,则会读取在@SPI注解上配置的值。
     比如这里配置的值是String[] {"key1", "key2"},首先通过key1从URL上寻找目标值作为扩展类的名字,找不到则使用key2继续寻找,没有找到则使用默认值,即@SPI注解配置的值(没有配置则转换为点分形式作为名称),如果是也没有则会抛出java.lang.IllegalStateException
     */
    String[] value() default {};

}

下面看一个简单的例子。

1:简单例子

源码

1.1:定义接口和实现类

@SPI("apple")
public interface FruitGranter {

  Fruit grant();

  @Adaptive
  String watering(URL url);
}

我们定义了一个水果种植类的接口FruitGranter,并通过设置@SPI("apple")来指定该接口为dubbo SPI接口,并设置要使用的默认的扩展类的名称是apple。另外在方法watering中配置了@Adaptive注解,代表需要根据URL中的参数来动态调用哪个扩展类方法,然后定义如下两个实现类:

// 苹果种植者
public class AppleGranter implements FruitGranter {

    @Override
    public Fruit grant() {
        return new Apple();
    }

    @Override
    public String watering(URL url) {
        System.out.println("watering apple");
        return "watering finished";
    }
}
// 香蕉种植者
public class BananaGranter implements FruitGranter {

  @Override
  public Fruit grant() {
    return new Banana();
  }

  @Override
  public String watering(URL url) {
    System.out.println("watering banana");
    return "watering success";
  }
}

定义了香蕉种植者和苹果种植者两个类,然后我们在配置文件中进行配置。

1.2:配置文件

首先在classpath下创建目录META-INF/dubbo,然后创建以FruitGranter全限定名称为文件名的文件,本文文件名称是dongshi.daddy.adaptive.FruitGranter,然后添加苹果扩展类和香蕉扩展类:

apple=dongshi.daddy.adaptive.AppleGranter
banana=dongshi.daddy.adaptive.BananaGranter

扩展类的名字分别是applebanana

1.3:测试

如下测试代码是使用名称为banana扩展类:

public class ExtensionLoaderTest {

  @Test
  public void testGetExtensionLoader() {
    // 首先创建一个模拟用的URL对象
    URL url = URL.valueOf("dubbo://192.168.0.101:20880?fruit.granter=banana");
    // 通过ExtensionLoader获取一个FruitGranter对象
    FruitGranter granter = ExtensionLoader.getExtensionLoader(FruitGranter.class)
      .getAdaptiveExtension();
    // 使用该FruitGranter调用其"自适应标注的"方法,获取调用结果
    String result = granter.watering(url);
    System.out.println(result);
  }
}

运行输出如下:

watering banana
watering success

如果是修改URL url = URL.valueOf("dubbo://192.168.0.101:20880?fruit.granter=banana");URL url = URL.valueOf("dubbo://192.168.0.101:20880");则会使用@SPI("apple")中设置的apple作为默认的扩展类名称,此时输出如下:

watering apple
watering finished

此时从URL上寻找扩展名称的key是FruitGranter接口简单名称的点分形式,即fruit.granter,我们也可以通过在FruitGranter#watering@Adaptive注解中设置key,比如设置为find.fruit.extenstion,如下:

@SPI("apple")
public interface FruitGranter {

  Fruit grant();

//  @Adaptive
  @Adaptive({ "find.fruit.extenstion" })
  String watering(URL url);
}

此时测试代码修改为如下也可以获取到名称为banana的扩展类:

public class ExtensionLoaderTest {

  @Test
  public void testGetExtensionLoader() {
    // 首先创建一个模拟用的URL对象
//    URL url = URL.valueOf("dubbo://192.168.0.101:20880?fruit.granter=banana");
    URL url = URL.valueOf("dubbo://192.168.0.101:20880?find.fruit.extenstion=banana");
//    URL url = URL.valueOf("dubbo://192.168.0.101:20880");
    // 通过ExtensionLoader获取一个FruitGranter对象
    FruitGranter granter = ExtensionLoader.getExtensionLoader(FruitGranter.class)
      .getAdaptiveExtension();
    // 使用该FruitGranter调用其"自适应标注的"方法,获取调用结果
    String result = granter.watering(url);
    System.out.println(result);
  }
}

测试输出如下:

watering apple
watering finished

Process finished with exit code 0

以上的例子,我们是将@Adaptive注解写到接口的方法上,此时dubbo会通过动态代理方式生成接口一个子类,并按照一定的逻辑实现该方法,逻辑其实也很简单,即根据最终获取到的参数值来获取对应的扩展类,并调用其方法,没有添加@Adaptive注解的方法会默认抛出异常信息,如下是本例生成的动态代码:

public class FruitGranter$Adaptive implements org.apache.dubbo.demo.example.eg19.FruitGranter {
  // 没有标注@Adaptive注解的方法,默认抛出java.lang.UnsupportedOperationException异常
  public org.apache.dubbo.demo.example.eg19.Fruit grant() {
    throw new UnsupportedOperationException(
        "The method public abstract org.apache.dubbo.demo.example.eg19.Fruit " 
      + "org.apache.dubbo.demo.example.eg19.FruitGranter.grant() of interface " 
      + "org.apache.dubbo.demo.example.eg19.FruitGranter is not adaptive method!");
  }
  
  // 该方法使用了@Adaptive注解,自动生成动态调用的逻辑 
  public java.lang.String watering(org.apache.dubbo.common.URL arg0) {
    // URL参数必须有值
    if (arg0 == null) {
      throw new IllegalArgumentException("url == null");
    }

    org.apache.dubbo.common.URL url = arg0;
    // 以fruit.granter作为key从url上获取参数值,默认值是apple,就是在注解@SPI("apple")配置的值
    String extName = url.getParameter("fruit.granter", "apple");
    if (extName == null) {
      throw new IllegalStateException(
          "Failed to get extension (org.apache.dubbo.demo.example.eg19.FruitGranter) name " 
        + "from url (" + url.toString() + ") use keys([fruit.granter])");
    }
    // 调用ExtensionLoader的方法根据扩展类名称获取对应的扩展实现类
    org.apache.dubbo.demo.example.eg19.FruitGranter extension =
      (org.apache.dubbo.demo.example.eg19.FruitGranter) ExtensionLoader
        .getExtensionLoader(org.apache.dubbo.demo.example.eg19.FruitGranter.class)
        .getExtension(extName);
    // 调用目标扩展实现类的watering方法
    return extension.watering(arg0);
  }
}

另外,@Adaptive注解还可以使用在接口的子类上,此时会直接执行该子类逻辑,在dubbo中目前只有ExtensionFactory的子类AdaptiveExtensionFactory是这种使用方法,用来分别从SPI和spring容器获取对象。下面我们再看一个将@Adptive注解使用在子类上的例子。

2:@Adaptive注解使用在子类上

源码

2.1:定义接口和实现类

2.1.1:接口
@SPI
public interface UseInSubClsInterface {
    void sayHi(String word);
}
2.1.2:非Adaptive实现类
public class ConcreteUseInSubClsInterface1 implements UseInSubClsInterface {
    @Override
    public void sayHi(String word) {
        System.out.println("ConcreteUseInSubClsInterface1 say hi: " + word);
    }
}
public class ConcreteUseInSubClsInterface2 implements UseInSubClsInterface {
    @Override
    public void sayHi(String word) {
        System.out.println("ConcreteUseInSubClsInterface2 say hi: " + word);
    }
}
2.1.3:Adaptive的实现类
@Adaptive
public class AdaptiveUseInSubClsInterface implements UseInSubClsInterface {
    // 封装所有的扩展类集合(@Adaptive注解的扩展类除外!!!)
    private final List<UseInSubClsInterface> factories;

    public AdaptiveUseInSubClsInterface() {
        // 获取接口UseInSubClsInterface所有的扩展类
        ExtensionLoader<UseInSubClsInterface> loader
                = ExtensionLoader.getExtensionLoader(UseInSubClsInterface.class);
        List<UseInSubClsInterface> list = new ArrayList<UseInSubClsInterface>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }
    @Override
    public void sayHi(String word) {
        // 任选一个扩展类来调用其方法
        int size = factories.size();
        Random random = new Random();
        factories.get(random.nextInt(size)).sayHi(word);
    }
}

为了测试目的,只是获取任意一个扩展类来调用。

2.2:定义SPI文件

文件名称dongshi.daddy.adaptive.useinsubcls.UseInSubClsInterface,内容如下:

adaptive=dongshi.daddy.adaptive.useinsubcls.AdaptiveUseInSubClsInterface
concreteUseInSubClsInterface1=dongshi.daddy.adaptive.useinsubcls.ConcreteUseInSubClsInterface1
concreteUseInSubClsInterface2=dongshi.daddy.adaptive.useinsubcls.ConcreteUseInSubClsInterface2

2.3:测试

public class AdaptiveInSubClsTest {
    @Test
    public void testGetExtensionLoader() {
        UseInSubClsInterface useInSubClsInterface
                = ExtensionLoader
                .getExtensionLoader(UseInSubClsInterface.class)
                // 获取Aaptive的子类
                .getAdaptiveExtension();
        useInSubClsInterface.sayHi("good night,mongo need to drink milk!!!");
    }
}

多次运行可以看到会随机调用,多次运行输出如下:

ConcreteUseInSubClsInterface2 say hi: good night,mongo need to drink milk!!!
ConcreteUseInSubClsInterface1 say hi: good night,mongo need to drink milk!!!

写在后面

参考文章列表:

Dubbo Adaptive机制详解

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Dubbo的@Service注解是用来标识一个类是一个Dubbo服务的提供者。这个注解会使得该类被扫描到Spring容器中,并生成对应的ServiceConfig对象。在Dubbo中,ServiceConfig是用来配置和暴露服务的。通过@Service注解Dubbo会自动为该类生成一个ServiceConfig对象,并将相关的配置信息注入到ServiceConfig中。具体的实现是通过ServiceClassPostProcessor的buildServiceBeanDefinition方法来完成的。该方法会遍历扫描到的每个BeanDefinition,并为每个类生成一个ServiceBean的BeanDefinition,将DubboService注解的信息注入到ServiceBean中,同时将类的第一个接口作为属性注入到ServiceBean中。因此,@Service注解Dubbo中用来标识服务提供者的重要注解之一。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [【转载】Dubbo注解@DubboService的机制](https://blog.csdn.net/lyf_9580/article/details/121478752)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Dubbo的@Reference和@Service说明](https://blog.csdn.net/ywb201314/article/details/106671462)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值