dubbo使用6----> spi 以及dubbo中的spi实现分析

1、java中的spi介绍

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

      java spi实现案例:

        1.1、定义一个接口ISayHelloService:

public interface ISayHelloService {
    void sayHello();
}

        1.2、实现一个中国人说你好:

public class ChineseSayHelloService implements ISayHelloService {
    public void sayHello() {
        System.out.println("你好!");
    }
}

         1.3、再实现一个老美说你好:

public class UsaSayHelloService implements ISayHelloService {
    public void sayHello() {
        System.out.println("hello !");
    }
}

          1.4、在resources目录下创建一个META-INF/services 目录:

                   

         1.5、在META-INF/services目录下创建一个文件,文件名称是ISayHelloService接口的全路径:

                  

          1.6、在以接口名称命名的文件中添加两个ISayHelloService接口的实现:

                  

         1.7、创建一个Client类用于测试java 的spi:

public class Client {
    public static void main(String[] args) {
        //ServiceLoader类实现了Iterable可以循环里面的每一个实现
        ServiceLoader<ISayHelloService> serviceLoader = ServiceLoader.load(ISayHelloService.class);
        serviceLoader.forEach(e->{
            e.sayHello();
        });
    }
}

         1.8、测试结果: 

         

   以上就是java spi的整体使用方式,其实就是依靠ServiceLoader来载入某接口的所有实现类,然后根据需求来选择具体的一个实现。

 

2、dubbo中的 spi 使用案例

      2.1、先引入dubbo的依赖,我们还是使用2.7.8的版本

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.8</version>
        </dependency>

      2.2、定义一个接口ISayHiService


 //这个注解很重要,如果没有标注的话,会报一个此接口不是扩展点的错误,注解的成员属性表示默认使用的实现的key。
@SPI(value = "chineseSayHi")
public interface ISayHiService {
    void sayHi();
}

      2.3、实现一个中国人说嗨

public class ChineseSayHiService implements ISayHiService {
    public void sayHi() {
        System.out.println("嗨 !");
    }
}

      2.4、实现一个美国人说hi

public class UsaSaytHiService implements ISayHiService {
    public void sayHi() {
        System.out.println("hi ! ");
    }
}

      2.5、在resources目录下创建一个META-INF/dubbo目录

               

      2.6、在META-INF/dubbo目录下创建一个ISayHiService全路径名称的文件

               

 

       2.7、在2.6中创建的文件中添加ISayHiService接口的实现

                

          2.8、创建一个Client进行测试

public class Client {
    public static void main(String[] args) {
        /*
            ExtensionLoader就是dubbo spi的实现,ExtensionLoader就是一个普通类,没有实现任何接口,
            它提供了通过名称来获取真正的实现类。
         */
        ExtensionLoader<ISayHiService> service = ExtensionLoader.getExtensionLoader(ISayHiService.class);

        ISayHiService chineseSayHi = service.getExtension("chineseSayHi");
        chineseSayHi.sayHi();

        ISayHiService usaSayHi = service.getExtension("usaSayHi");
        usaSayHi.sayHi();
    }
}

         2.9、测试结果展示

                 

 

3、dubbo spi 与java spi的区别

      由上面的案例我们不难看出,java 的spi在加载服务的时候,返回值是一个可迭代的服务列表,也就是说在加载服务这个过程java spi的ServiceLoader<T> 会加载T类型的所有的实现并全部实例化,然后我们需要循环遍历找到自己想要的具体的服务实现。

      而dubbo spi的实现方式有所区别,因为dubbo spi为每一个实现取了一个名字,然后通过名字取加载自己想要的实现,自己不想要的就不会加载,比较灵活,也不会造成加载浪费,这个算是最主要的一个区别。

 

4、dubbo 中的扩展机制

      4.1、我们可以在dubbo的源码中看到各种可以扩展的地方,如LoadBalance、Protocol、Filter等等,而这些扩展的地方都是使用dubbo spi来进行扩展的。

               案例:dubbo中通过名称获取到负载均衡的算法

       RandomLoadBalance rd = 
           (RandomLoadBalance)ExtensionLoader.
                             getExtensionLoader(Loadbalance.class).getExte nsion("random");

      4.2、接下来我们来讲解一下dubbo 中的扩展机制有哪些?

               1、指定名称的扩展机制:例如:ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("name");这种事最常用的一种扩展机制。

               2、自适应扩展点:先借用官方的一段话 “有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。”

                    这段话的意思就是比如我需要用一个对象才能调用某该对象的非static方法,但是呢我没有这对象实例,这个对象实例呢又必须在我需要调用的方法里面才能生成。这就特别矛盾了,dubbo的自适应扩展机制就是解决这个问题的,那么dubbo的自适应机制的核心思想是啥呢?

                    dubbo的自适应的核心机制就是,我没办法先获取到对象再去调用所需要的的方法,那么我就构建一个是是配置器对象,这个适配器呢跟我所需要的对象实现同一个接口,我在这个适配器中先实例化所需要的对象然后再使用其去调用所需要的调用的方法。然后呢我就调用这个适配器的跟我最终需要调用的同名称的方法。

                    说白了,dubbo的自适应扩展机制就是创建适配器,然后在适配器里面实现通过某些参数,如url中的protocol参数来确定扩展点的名称,然后加载具体的实现类并调用其业务方法。 dubbo的适配器是动态创建的,也就是说这个适配器不是程序员事先编写好的,而是在执行期间动态生成代码(code), 然后获取一个Compile编译器在执行期间进行编译,然后构建实例。

                    dubbo自适应扩展机制为啥要这么做呢?实事先编译好适配器的源码直接使用其源码创建对象来使用不行吗???答案是当然可以,但是dubbo这么做也有它的道理,比如我们需要的适配器特别多的时候,我们就需要投入更多的代码维护,如果后面变更比较大的话,逐个去修改适配器将会比较难维护,如果动态生成,那我只需要按需求修改生成适配器源码的规则即可,这样就比较符合开闭原则。

                    案例1:ServiceConfig类中生成Protocol的适配器

       这就是获取一个自适应扩展的协议,也就是我们说的适配器,
       在这个适配器中实现了会在运行期间根据url中配置的protocol名
       称来加载具体的Protocol实现。
       private static final Protocol PROTOCOL = 
                 ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

                     案例2:ServiceConfig类中生成ProxyFactory的适配器

       这里就是生成一个ProxyFactory的适配器,这个适配器里面实现
       了根据配置的proxy属性来加载具体的ProxyFactory实现类进行使用。
       private static final ProxyFactory PROXY_FACTORY = 
             ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

                3、激活扩展点:激活扩展点的含义就是根据条件来进行获取扩展点,比如dubbo的url中存在token参数那就能够去激活TokenFilter,然后就能够拿到这个扩展的实现。

                     案例:dubbo中获取Filter扩展点的实现列表

       获取到一个Filter过滤器的扩展加载器
       ExtensionLoader extensionLoader=ExtensionLoader.getExtensionLoader(Filter.class); 

       构建一个Url实例
       URL url=new URL("","",0); 
       向Url中添加cache 配置 value=lru
       url=url.addParameter("cache","lru"); 

       获取激活区扩展点,入参key=cache 表示在url中参数名称
       List<Filter> filters=extensionLoader.getActivateExtension(url,"cache"); 

                   

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值