Dubbo高级进阶Spi应用以及与JDK的Spi区别

Dubbo官网

DubboSpi

Dubbo高级进阶Spi应用

Dubbo是由阿里巴巴开源的一款高性能、轻量级的开源Java Rpc(远程过程调用)框架,提供三大核心能力:面向接口的远程方法调用、智能容错和负载均衡、服务自动注册与发现。

在Dubbo的源码中,下面这种句式出现比较多,比如如下句式:通过ExtensionLoader获取Protocol接口的代理类。

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();   

仔细翻看dubbo中的源码,Protocol接口的实现类有很多种,那么在程序的执行中怎么得到对应的实现类,怎么去动态的扩展接口实现,这些问题就是今天讨论的重点。

一、Spi是什么。

Spi全称为(Service Provider Interface),是Jdk内置的一种服务提供发现机制,目前有不少框架用他来做服务的拓展发现,简单说Spi是一种动态替换发现机制,使用Spi机制的优势是实现解耦,使第三方服务模块的装配控制逻辑与调用者的业务代码分离。

二、Java中JDK的Spi实现。

  Java中如果想要使用SPI功能,先提供标准服务接口,然后在提供相关接口实现的调用者,这样就可以通过spi机制中约定好的信息进行查询相应的接口实现

  SPI遵循以下约定

  1)当服务提供者提供了一个服务(接口)的具体实现后,在classpath下的META-INF/services目录下创建一个以“接口全限定名”命名的文件,内容为实现类的全限定名。

  2)接口实现类所在的jar包放在驻车鞥徐的classpath中;

  3)主程序通过java.util.ServiceLoader动态状态实现模块(类),它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM。

  4)Spi的实现类必须携带一个无参构造

  JDK中的Spi机制应用比较广泛比如说common-logging、JDBC等,这里使用JDBC进行举例

1、JDBC接口定义
  首先在java 中定义接口java.sql.Dirver并没有具体的实现,具体的实现由不同的厂商来提供
2、mysql实现
  在mysql的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。
3、查找调用在DriverManager类的静态代码块 loadInitialDrivers();方法中

  感兴趣的小伙伴可以去翻看源码哈~这里我就不展示源码了,做一个简单的模拟案例进行展示,项目目录如下所示

源码如下:

 Driver类

package city.albert;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/8/25  5:09 PM
 */
public interface Driver {

    /**
     * 模拟获取驱动名称
     * @return
     */
    String driverName();
}
View Code

MysqlDriver实现类

package city.albert.impl;

import city.albert.Driver;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/8/25  5:12 PM
 */
public class MysqlDriver implements Driver {
    @Override
    public String driverName() {
        return "mysql 驱动";
    }
}
View Code

OricalDriver实现类

package city.albert.impl;

import city.albert.Driver;
/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/8/25  5:14 PM
 */
public class OricalDriver implements Driver{
    @Override
    public String driverName() {
        return "orical 驱动";
    }
}
View Code

项目的classpath下META-INF/services目录下创建名为“city.albert.Driver”的文件夹指定你的实现类即可,我这里两个类同时指定内容为:

city.albert.impl.MysqlDriver
city.albert.impl.OricalDriver

测试类如下

package city.albert.impl;

import city.albert.Driver;

import java.util.ServiceLoader;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/8/25  5:17 PM
 */
public class TestMain {

    public static void main(String[] args) {
        ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
        for (Driver driver : drivers) {
            System.out.println(driver.driverName());
        }
    }
}

结果两个实现类都被加载

三、Dubbo中的Spi实现。

  区别:在Dubbo的框架中并没有使用JDK的Spi来实现,而是重写了Spi的实现,Dubbo中的Spi形式上都是从Jar中加载对应的拓展类,但是Dubbo支持更多的加载路径,也不是通过Iterator的形式调用而是通过名称来定位具体的Provider,按照需要进行加载,并非Jdk中的一次性全部加载,效率更高,同时支持Provider以类似IOC的形式提供。

Dubbo自己实现Spi的目的

1、JDK标准的SPI会一次性实例化拓展点的所有实现,如果所有的实现初始化很耗时,并没加载上也没有用,就会很浪费资源。
2、如果有的拓展点加载失败,则所有的拓展点无法使用。
3、提供了对拓展点包装的功能(Adaptive),并且还支持Set的方式对其他拓展点进行注入

  Dubbo中实现Spi与JDK形式上的区别

  1、接口上需要添加“org.apache.dubbo.common.extension”包下的@SPI注解,在@SPI("spiService")注解中可以指定默认拓展点。

  2、在META-INF/dubbo目录下创建全限定名文件

  3、全限定名文件中的内容是KEY-VALUE形式,value是实现类的全限定类名

  4、调用者使用ExtensionLoader获取加载

具体实现代码

api项目中需要引入bubbo依赖因为使用到@Spi注解,版本号根据自己的选型来定哈~

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

api代码

package city.albert;

import org.apache.dubbo.common.extension.SPI;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/8/25  5:49 PM
 */
@SPI
public interface DubboSpiService {
    String getName();
}
View Code

实现类

package city.albert.impl;

import city.albert.DubboSpiService;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/8/25  5:51 PM
 */
public class DubboSpiServiceImpl implements DubboSpiService{
    @Override
    public String getName() {
        return "dubbo——实现类——getName";
    }
}
View Code

META-INF/dubbo/city.albert.DubboSpiService文件内容

spiService = city.albert.impl.DubboSpiServiceImpl

调用类main

package city.albert;

import org.apache.dubbo.common.extension.ExtensionLoader;

/**
 * @author niunafei
 * @function
 * @email niunafei0315@163.com
 * @date 2020/8/25  5:53 PM
 */
public class DubboMain {
    public static void main(String[] args) {
        ExtensionLoader<DubboSpiService> loader = ExtensionLoader.getExtensionLoader(DubboSpiService.class);
        DubboSpiService service = loader.getExtension("spiService");

        System.out.println(service.getName());
    }
}

原文地址:https://www.cnblogs.com/niunafei/p/13553096.html