上两篇已经深入分析了 Java9新特性系列(深入理解模块化),以及
Java9新特性系列(module&maven&starter),有读者又提到了与模块化相关的
spi
,本篇将进行分析。
SPI是什么?
提到SPI呢,就不得不提一下API:
API:Application Programming Interface,即应用程序编程接口,在程序外部进行调用
SPI:Service Provider Interface,服务提供商接口
SPI核心思想是什么?
我们知道,一个系统中会有很多模块,比如数据库模块、日志模块、调度模块、各种业务模块等等,每一类的模块都有很多种实现, 数据库可以用mysql、oracle等,日志可以用log4j、logback等,那么对于不同的场景有不同的选型,如何能做到可插拔呢,那就是SPI了。
可插拔原则可以理解为系统与插件的关系,系统提供了一些接口,第三方插件进行实现。
面向接口编程,不绑定实现,为了在模块装配的时候不在代码中动态去指定具体的实现,就需要去发现具体的实现,即服务发现,其实就类似于回调,只不过回调的时候需要找到具体的实现,spi就帮我们做了去寻找实现的工作。
这一思想在模块化设计中尤为重要。
SPI实现方式?
SPI具体的实现方式分两种:
- 应用系统自身提供默认实现
- 第三方提供实现
SPI有什么例子呢?
下面我们就以jdk中的jdbc模块儿进行分析:
package java.sql;
import java.util.logging.Logger;
public interface Driver {
Connection connect(String url, java.util.Properties info)
throws SQLException;
boolean acceptsURL(String url) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
复制代码
我们可以看到,java.sql.Driver接口定义了每个实现必须实现的一些方法,接下来我们就看具体的实现:
- mysql
META-INF/services/java.sql.Driver
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
复制代码
在这个文件中指定了具体的实现
- oracle
META-INF/services/java.sql.Driver
oracle.jdbc.OracleDriver
复制代码
在JDBC4以前,我们还需要使用比如Class.forName("com.mysql.jdbc.Driver")的方式来装载驱动。
如上图所示:
JDBC也基于spi的机制来发现驱动提供商了,可以通过META-INF/services/java.sql.Driver
文件里指定实现类的方式来暴露驱动提供者。
其中,META-INF/services/
是固定的,java.sql.Driver
为接口对应的package
,文件中为具体的实现类。
SPI如何被框架发现呢?
框架可以使用java提供的java.util.ServiceLoader类得到SPI的实现。我们来看下java.sql.DriverManager中是如何去发现的:
private static void loadInitialDrivers() {
...
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 获取具体的实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
...
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);
}
}
}
复制代码
使用
ServiceLoader<XXX> loadedDrivers = ServiceLoader.load(XXX.class);
,实际上就是到具体的路径下读取文件内容。
SPI应用
用过阿里Dubbo
的开发者都知道,Dubbo对JDK中SPI进行了扩展和改进,这个在以后dubbo相关的文章中再进行介绍~
好了,本期就讲到这里,下期我们讲讲Java9中到另一个新工具
JShell
,敬请期待~