1.Java SPI是什么?
SPI(Servive Provider Interface),是JDK内置的一种服务发现机制,可以用来替换实现组件。具体逻辑上来说:
某个框架提供一种标准的接口定义,框架内部调用这个接口定义的方法来实现某个功能。而功能是如何实现的,是否在原有功能上有增强等等,框架不关心,每个服务实现的提供方去实现。这样一来,框架就可以实现对某个组件的拔插式使用,达到解耦的目的,提供框架整体的可扩展性。
图片来源(pdai.tech/md/java/adv…)
2.Java SPI能做什么?
SPI的主要目的就是提供一种服务发现的能力,从而达到程序的可扩展性。目前在使用的案例就有很多,比如JDBC的连接、Dubbo的改进版SPI等。
2.1 JDBC使用SPI
各个不同的数据库厂商都会提供一个java.sql.Driver的实现类,用来向java.sql.DriverManager来注册Driver。
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//xxx do something
}
public class OracleDriver implements Driver, Monitor {
//xxx do something
}
下面以Mysql为例来说明Mysql驱动Driver加载的流程
之前是Class.forName("全类名")进行加载驱动的逻辑,现在通过SPI即可获取数据库驱动。
(1)主要的实现逻辑在DriverManager逻辑中,其获取驱动的代码如下:
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
(2)主要是通过loadInitialDrivers()方法去加载驱动,使用ServiceLoader的机制去扫描实现jar中的类,跟踪代码,它会去mysql jar包下META-INF包下service目录下的实现类的全类名,然后加载这个类com.mysql.cj.jdbc.Driver。该类下有也有一个静态代码块,会调用DriverManager的registerDriver()方法将自己注册到registeredDrivers中。
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());//代码说明:服务提供者自己实例化
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
(3)DriverManager中的registerDriver()方法是将扫描到所有Driver加入到CopyOnWriteArrayList数组中,具体代码如下:
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
(4)后续代码会循环遍历registeredDrivers,将我们配置的jdbcurl传入,来判断该Driver是否是正确的,可识别的,如果是,则获取相应的驱动,完成Driver实例化。
2.2 Dubbo改进版SPI
这个见后续Dubbo源码分析中的系列文章
3.Java SPI怎么使用?
现在编写一个sayHelloWorld接口,然后根据不同的语言来输出say hello world。比如:中文的话输出"你好,世界!", 英文的话输出"Hello World"
(1)定义一个接口SayHelloWorld,它包含方法helloWorld(),如下:
public interface SayHelloWorld {
void helloWorld();
}
(2)在resource下定义一个META-INF/services包,包下创建名为接口com.zys.dubbo.demo.SayHelloWorld的文件,文件中包含两个实现类
com.zys.dubbo.demo.service.impl.SayHelloInChinese
com.zys.dubbo.demo.service.impl.SayHelloInEnglish
public class SayHelloInChinese implements SayHelloWorld {
@Override
public void helloWorld() {
System.out.println("你好,世界!");
}
}
public class SayHelloInEnglish implements SayHelloWorld {
@Override
public void helloWorld() {
System.out.println("Hello, World!");
}
}
(3)编写一个测试类
@SpringBootTest
public class TestSPI {
@Test
public void test01(){
ServiceLoader<SayHelloWorld> loader = ServiceLoader.load(SayHelloWorld.class);
Iterator<SayHelloWorld> iterator = loader.iterator();
while (iterator.hasNext()){
SayHelloWorld say = iterator.next();
say.helloWorld();
}
}
}
上述输出结果会有"你好,世界" 和 "Hello World!"
4.深入分析
todo