Dubbo
的扩展点加载机制类似于Java
的SPI
加载机制,但是Java
的SPI
加载机制在查找具体某个实现的时候,只能通过遍历进行查找并会实例化所有实现类,因此对于实际不需要加载的扩展实现也会实例化,造成一定的内存浪费。Dubbo SPI
加载机制可通过扩展点名称进行查找,避免实例化所有实现;同时,增加了对扩展点IoC和AOP的支持,一个扩展点实现可以注入其他扩展点实现并进行Wrapper
包装。
在了解Dubbo SPI
机制之前我们先了解一下Java
的SPI
机制。
Java SPI
以MySQL数据库驱动为例,来看一下Java SPI
是如何实现的。
首先,MySQL软件驱动包在META-INF/services目录下创建了java.sql.Driver
文件
java.sql.Driver
文件如下,内容为具体实现类的全路径名
DriverManager
类在被虚拟机加载时会运行如下代码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
其中loadInitialDrivers()
就是DriverManager
使用SPI
机制加载mysql
声明的驱动,loadInitialDrivers()
核心代码如下:
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
参照MySQL数据库驱动总结一下Java SPI具体的实现步骤:
- 定义一个接口及对应的方法
- 实现这个接口
- 在
META-INF/services
下创建一个亿接口全路径命名的文件,如:java.sql.Driver
- 文件内容为具体实现类的全路径名,如果有多个,就用分行符号分隔
- 在代码中通过
ServiceLoader
来加载具体类的实现类
但是Java SPI
也是有一些缺陷的
- JDK自带的SPI会一次性实例化所有扩展点实现,如果扩展点不使用,那么会浪费资源
- 在扩展点加载失败的情况下,JDK扩展点加载机制无法提供扩展点加载失败的真正原因
- JDK自带SPI机制不支持IOC和AOP的功能
Dubbo SPI
相比Java SPI
,Dubbo SPI
做了一些改进和优化:
- 相对于 Java SPI 一次性加载所有实现,Dubbo SPI 是按需加载,只加载需要使用的实现类
- 更为详细的扩展加载失败信息
- 增加了对扩展 IOC 和 AOP的支持
Dubbo SPI和Java SPI类似,需要在META-INF/dubbo/接口全限定名
配置对应的SPI配置文件,文件内容为key=扩展点实现类的全路径名称
,如果有多个实现,则用换行符分隔。其中,key会作为Dubbo SPI注解中的传入参数。另外,Dubbo SPI还兼容了Java SPI的配置路径和内容配置方式。在Dubbo启动的时候,会默认扫描这三个目录下的配置文件:META-INF/services、META-INF/dubbo/、META-INF/dubbo/internal
,如下所示:
规范名 | 规范说明 |
---|---|
SP配置文件路径 | META-INF/services、META-INF/dubbo/、META-INF/dubbo/internal |
SPI配置文件名称 | 全路径类名 |
文件内容格式 | key=value,多个用换行符分隔 |
Dubbo SPI 示例,来自官网
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。
- 定义一个
Robot
接口
@SPI
public interface Robot {
void sayHello();
}
- 接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
- 接下来 META-INF/dubbo 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
- 与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注
@SPI
注解。
public class DubboSPITest {
@Test
public void sayHello() throws Exception {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}
测试结果如下: