Dubbo的SPI原理

前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类

正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。

1、SPI 实例

1.1、Java SPI

1.1.1、接口**Robot**

public interface Robot {
   

    void sayHello();

}
1.1.1.1、实现类OptimusPrime
@Slf4j
public class OptimusPrime implements Robot {
   

    @Override
    public void sayHello() {
   
      log.info("Hello, I am Optimus Prime.");
    }
}
1.1.1.2、实现类Bumblebee
@Slf4j
public class Bumblebee implements Robot {
   


    @Override
    public void sayHello() {
   
        log.info("Hello, I am Bumblebee.");
    }
}

1.1.2、 接口全限名配置文件

resources -> META-INFO ->services

com.healerjean.proj.study.spi.OptimusPrime
com.healerjean.proj.study.spi.Bumblebee

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MTOIpGAu-1594897494549)(https://raw.githubusercontent.com/HealerJean/HealerJean.github.io/master/blogImages/image-20200627155645060.png)]

1.1.3、Main方法测试

@Slf4j
public class JavaSPITest {
   

    @Test
    public void sayHello() {
   
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        log.info("Java SPI--------");
        serviceLoader.forEach(Robot::sayHello);
    }
}
1.1.3.1、控制台日志
2020-06-27 15:58:02 INFO  -[                                ]- Java SPI-------- com.healerjean.proj.study.spi.java.JavaSPITest.sayHello[21]
2020-06-27 15:58:02 INFO  -[                                ]- Hello, I am Optimus Prime. com.healerjean.proj.study.spi.OptimusPrime.sayHello[16]
2020-06-27 15:58:02 INFO  -[                                ]- Hello, I am Bumblebee. com.healerjean.proj.study.spi.Bumblebee.sayHello[17]

1.2、Dubbo SPI

继续使用上面的代码,对其进行改动一点点

1.2.1、接口添加注解@SPI

@SPI
public interface Robot {
   

    void sayHello();

}

1.2.2、配置文件

dubbo配置文件一般在dubbo文件夹中,但是其实通过后面的源码我们也可以知道,不一定非要在这个文件夹中,其他文件夹也是可以的

optimusPrime = com.healerjean.proj.study.spi.OptimusPrime
bumblebee = com.healerjean.proj.study.spi.Bumblebee

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h6jkaSvC-1594897494551)(https://raw.githubusercontent.com/HealerJean/HealerJean.github.io/master/blogImages/image-20200627160249742.png)]

1.2.3、Main方法启测试

@Slf4j
public class DubboSPITest {
   

    @Test
    public void sayHello()  {
   
        ExtensionLoader<Robot> extensionLoader =  ExtensionLoader.getExtensionLoader(Robot.class);

        log.info("dubbo SPI--------");
        log.info("----------------------------");
        log.info("从配置文件中获取--------");
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
        log.info("----------------------------");

        log.info("{}", extensionLoader.getSupportedExtensions());


    }
1.2.3.1、控制台日志
2020-06-27 16:04:19 INFO  -[                                ]- using logger: com.alibaba.dubbo.common.logger.slf4j.Slf4jLoggerAdapter com.alibaba.dubbo.common.logger.LoggerFactory.[]
2020-06-27 16:04:19 INFO  -[                                ]- dubbo SPI-------- com.healerjean.proj.study.spi.dubbo.DubboSPITest.sayHello[21]
2020-06-27 16:04:19 INFO  -[                                ]- ---------------------------- com.healerjean.proj.study.spi.dubbo.DubboSPITest.sayHello[22]
2020-06-27 16:04:19 INFO  -[                                ]- 从配置文件中获取-------- com.healerjean.proj.study.spi.dubbo.DubboSPITest.sayHello[23]
2020-06-27 16:04:20 INFO  -[                                ]- Hello, I am Optimus Prime. com.healerjean.proj.study.spi.OptimusPrime.sayHello[16]
2020-06-27 16:04:20 INFO  -[                                ]- Hello, I am Bumblebee. com.healerjean.proj.study.spi.Bumblebee.sayHello[17]
2020-06-27 16:04:20 INFO  -[                                ]- ---------------------------- com.healerjean.proj.study.spi.dubbo.DubboSPITest.sayHello[28]
2020-06-27 16:04:20 INFO  -[                                ]- ["bumblebee","optimusPrime"] com.healerjean.proj.study.spi.dubbo.DubboSPITest.sayHello[30]

1.3、@Adaptive 注解使用

在类,以及方法上调用。定义了 注解的方法,参数列表中一定要有类型为 URL 的参数。

value 是个字符数组,通过该属性从 URL 中获取扩展名,来决定使用哪个扩展。分为几种情况:

1.设置了 value,且从 URL 中找到了对应的扩展名,则使用该扩展;

2.设置了 value,但从 URL 中找不到扩展名,则使用默认的扩展,即@SPI 中配置的 value,还是找不到则抛出异常;

3.未设置 value,则根据接口名生成 value,比如接口 Animal生成 value = “animal”。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
   ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
   

}

1.3.1、实例说明

1.3.1.1、实例1

@SPI注解中有value值

1.3.1.1.1、示例代码

接口

@SPI(value = "adaptiveAnimal")
public interface Animal {
   

     @Adaptive
    void call(String msg, URL url);

}

扩展类

@Slf4j
public class AdaptiveAnimal implements Animal {
   

    @Override
    public void call(String msg, URL url) {
   
      log.info("我是适配动物,发出叫声:{}", msg);
    }

}

@Slf4j
public class Cat implements Animal {
   

    @Override
    public void call(String msg, URL url) {
   
        log.info("我是猫,发出叫声: {},", msg);
    }

}

@Slf4j
public class Dog implements Animal {
   

    @Override
    public void call(String msg, URL url) {
   
        log.info("我是狗,发出叫声: {},", msg);
    }

}

1.3.1.1.2、启动测试
/**
* 1、 @SPI注解中有value值,URL中没有具体的值
* 2、@SPI注解中有value值,URL中也有具体的值
*/
@Test
public void test1()  {
   
    ExtensionLoader<Animal> annoimalExtensionLoader =  ExtensionLoader.getExtensionLoader(Animal.class);
    Animal adaptiveExtension = annoimalExtensionLoader.getAdaptiveExtension();
    //1、 @SPI注解中有value值,URL中没有具体的值
    URL url = URL.valueOf("test://localhost/test");
    adaptiveExtension.call("哒哒哒", url); //我是适配动物,发出叫声:哒哒哒

    //2、@SPI注解中有value值,URL中也有具体的值
    url = URL.valueOf("test://localhost/test?animal=cat");
    adaptiveExtension.call("喵喵喵", url);  //  我是猫,发出叫声: 喵喵喵
}

控制台日志

我是适配动物,发出叫声:哒哒哒 
我是猫,发出叫声: 喵喵喵
1.3.1.1.3、中间dubbo生成的适配扩展类
package com.healerjean.proj.study.spi;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Animal$Adaptive implements com.healerjean.proj.study.spi.Animal {
   
    public void call(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
   
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        
        //获取animal,如果获取不到则选择adaptiveAnimal
        String extName = url.getParameter("animal", "adaptiveAnimal");
        if (extName == null) 
            throw new IllegalStateException("Fail to get extension(com.healerjean.proj.study.spi.Animal) name from url(" + url.toString() + ") use keys([animal])");

        com.healerjean.proj.study.spi.Animal extension = (com.healerjean.proj.study.spi.Animal) 
            ExtensionLoader.getExtensionLoader(com.healerjean.proj.study.spi.Animal.class).getExtension(extName);
        
        extension.call(arg0, arg1);
    }
}
1.3.1.2、实例2

@SPI注解中有value(dog)值,URL中也有具体的值(cat),实现类AdaptiveAnimal上有@Adaptive注解

1.3.1.2.1、示例代码

接口

@SPI(value = "dog")
public interface Animal {
   

    @Adaptive
    void call(String msg, URL url);

}

扩展类

@Adaptive
@Slf4j
public class AdaptiveAnimal implements Animal {
   

    @Override
    public void call(String msg, URL url) {
   
      log.info("我是适配动物,发出叫声:{}", msg);
    }

}


@Slf4j
public class Cat implements Animal {
   

    @Override
    public void call(String msg, URL url) {
   
        log.info("我是猫,发出叫声: {},", msg);
    }

}


@Slf4j
public class Dog implements Animal {
   

    @Override
    public void call(String msg, URL url) {
   
        log.info("我是狗,发出叫声: {},", msg);
    }

}
1.3.1.3.2、启动测试
@Test
public void test3()  {
   
    ExtensionLoader<Animal> annoimalExtensionLoader =  ExtensionLoader.getExtensionLoader(Animal.class);
    Animal adaptiveExtension = annoimalExtensionLoader.getAdaptiveExtension();
    URL url = URL.valueOf("test://localhost/test?animal=cat");
    adaptiveExtension.call("哒哒哒", url); //我是适配动物,发出叫声:哒哒哒
}

控制台日志:

我是适配动物,发出叫声:哒哒哒
1.3.1.2.3、中间dubbo不会生成

不会帮我们生成,因为,这个时候使用了注解@Adaptive,则这个类就是适配扩展类,

那如果有多个类给注解了呢,则只会选中一个

1.3.1.4、实例4

@SPI注解中有value值,实现类上没有@Adaptive注解,方法上的@Adaptive注解,注解中的value与链接中的参数的key一致,链接中的key对应的value就是spi中的name,获取相应的实现类。

1.3.1.4.1、实例代码

接口:

@SPI(value = "adaptiveAnimal")
public interface Animal {
   

    @Adaptive(value = "name")
    void call(String msg, URL url);

}

扩展类

@Slf4j
public class AdaptiveAnimal implements Animal {
   

    @Override
    public void call(String msg, URL url) {
   
      log.info("我是适配动物,发出叫声:{}", msg);
    }

}


@Slf4j
public class Dog implements Animal {
   

    @Override
    public void call(String msg, URL url) {
   
        log.info("我是狗,发出叫声: {},", msg);
    }

}

@Slf4j
public class Cat implements Animal {
   

    @Override
    public void call(String msg, URL url) {
   
        log.info("我是猫,发出叫声: {},", msg);
    }

}
1.3.1.4.2、启动测试

@Test
public void test4()  {
   
    ExtensionLoader<Animal> annoimalExtensionLoader =  ExtensionLoader.getExtensionLoader(Animal.class);
    Animal adaptiveExtension = annoimalExtensionLoader.getAdaptiveExtension();
    URL url = URL.valueOf("test://localhost/test?name=dog");
    adaptiveExtension.call("汪汪汪", url); //我是狗,发出叫声: 汪汪汪
}

控制台日志:

我是狗,发出叫声: 汪汪汪
1.3.1.3.3、中间dubbo生成的适配扩展类
package com.healerjean.proj.study.spi;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Animal$Adaptive implements com.healerjean.proj.study.spi.Animal {
   
	public void call(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
   
		if (arg1 == null) throw new IllegalArgumentException("url == null");
		com.alibaba.dubbo.common.URL url = arg1;
        
        //获取url路径中key为name的值,获取不到的话,就选中adaptiveAnimal
		String extName = url.getParameter("name", "adaptiveAnimal");
		if (extName == null) throw new IllegalStateException("Fail to get extension(com.healerjean.proj.study.spi.Animal) name from url(" + url.toString() + ") use keys([name])");
		com.healerjean.proj.study.spi.Animal extension = (com.healerjean.proj.study.spi.Animal) ExtensionLoader.getExtensionLoader(com.healerjean.proj.study.spi.Animal.class).getExtension(extName);
		extension.call(arg0, arg1);
	}
}

1.3.2、归纳总结

1、在类上加上@Adaptive注解的类,是最为明确的创建对应类型Adaptive类。所以他优先级最高

2、@SPI注解中的value是默认值,如果通过URL获取不到关于取哪个类作为Adaptive类的话,就使用这个默认值,当然如果URL中可以获取到,就用URL中的。

3、方法上的@Adaptive注解,注解中的value与链接中的参数的key一致,链接中的key对应的value就是spi中的name,获取相应的实现类。

1.4、@Activate

1、根据loader.getActivateExtension中group和搜索到此类型的实例进行比较,如果group能匹配到,就是我们选择的,也就是在此条件下需要激活的

2、@Activate中的value是参数是第二层过滤参数(第一层是通过group),在group校验通过的前提下,如果URL中的参数(k)与值(v)中的参数名同@Activate中的value值一致或者包含,那么才会被选中。相当于加入了value后,条件更为苛刻点,需要URL中有此参数并且,参数必须有值。

3、@Activateorder参数对于同一个类型的多个扩展来说,order值越小,优先级越高。

接着上面的代码

@Activate(group = "default_group", value = "valueAc")
@Slf4j
public class Dog implements Animal {
   

    @Override
    public void call(String msg, URL url) {
   
        log.info("我是狗,发出叫声: {},", msg);
    }

}

@Test
public void getActive() {
   
    ExtensionLoader<Animal> annoimalExtensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);
    URL url = URL.valueOf("test://localhost/test");
    List<Animal> list = annoimalExtensionLoader.getActivateExtension(url, new String[]{
   }, "default_group");
    list.stream().forEach(System.out::println); //null

    log.info("-----------------");
    
    url = URL.valueOf("test://localhost/test?valueAc=fasdjafjdklj");
    list = annoimalExtensionLoader.getActivateExtension(url, new String[]{
   }, "default_group");
    list.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值