Dubbo高级进阶Spi应用以及与JDK的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(); }
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 驱动"; } }
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 驱动"; } }
项目的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(); }
实现类
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"; } }
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()); } }