SPI(Service Provider Interface)机制
SPI简单示例 SPI之DriverManager
1.原始方式2.SPI方式
SPI(Service Provider Interface)机制SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用。
Java中SPI机制主要思想是将装配的控制权移到程序之外(这里的意思是开发人员不需要显示的加载接口的对应实现,而是交由SPI机制来加载),在模块化设计中这个机制尤其重要,其核心思想就是 解耦
如下图:
当服务的提供者提供了一种接口的实现之后,需要在classpath下的meta-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的meta-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。
我们现在需要使用一个内容搜索接口,搜索的实现可能是基于文件系统的搜索,也可能是基于数据库的搜索。
1.先定义接口
public interface Search {
public List searchDoc(String keyword);
}
2.文件搜索实现
public class FileSearch implements Search{
@Override
public List searchDoc(String keyword) {
System.out.println("文件搜索 "+keyword);
return null;
}
}
3.数据库搜索实现
public class DatabaseSearch implements Search{
@Override
public List searchDoc(String keyword) {
System.out.println("数据搜索 "+keyword);
return null;
}
}
3.resources 接下来可以在resources下新建meta-INF/services/目录,然后创建名字为接口全限定类名的文件,里面加上我们需要用到的实现类(可以包含多个):
4.进行测试
ServiceLoaderload = ServiceLoader.load(Search.class); Iterator iterator = load.iterator(); while (iterator.hasNext()){ Search search = iterator.next(); search.searchDoc("hello word"); }
测试结果:
数据搜索 hello word 文件搜索 hello word
这就是spi的思想,接口的实现由provider实现,provider只用在提交的jar包里的meta-INF/services下根据平台定义的接口新建文件,并添加进相应的实现类内容就好。
SPI之DriverManager 1.原始方式首先,大家是否熟悉如下这段代码?
//1.加载MySQl驱动
Class.forName("com.mysql.jdbc.Driver");
//2.指明url
String url = "jdbc:mysql://localhost:3306/databasename";
//3.通过DriverManager获取connection
Connection conn = DriverManager.getConnection(url, username, password);
当时在学习这一段的时候有无好奇过,为什么第一步加载了驱动类,第三步就可以建立一个MySQL的连接。从代码上看他们直接并没有直接的联系。但是当我们打开MySQL驱动类时可以看到如下一段代码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager 将自己注册到DriverManager中
//
// 可以出这是一个静态代码块,我们都知道,当使用到该类的时候,一定会执行类的静态代码块!
static {
try {
//这句话至关重要,他调用了DriverManager的registerDriver()方法,
//把自己注册到了DriverManager中,从这里看是不是豁然开朗了,执行第一步的
//Class.froname("XXX")时,会执行这个静态代码块,而这个静态代码块,将自己
//给了DriverManager;
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
接下来看一下java.sql.DriverManager.registerDriver(new Driver());的具体逻辑:
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
if(driver != null) {
//private final static CopyOnWriteArrayList registeredDrivers
//= new CopyOnWriteArrayList<>();
// 从这一句可以看到,他把driver封装成一个 DriverInfo 放到了一个静态的线程安全的数组中
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
最后看一下DriverManager.getConnection(url, username, password);的具体过程,程序遍历上一步中的集合,进行连接的建立,当建立成功,就返回。
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
2.SPI方式
在引入数据库驱动的情况下,使用如下代码也可以正常获取连接:
//2.指明url String url = "jdbc:mysql://localhost:3306/databasename"; //3.通过DriverManager获取connection Connection conn = DriverManager.getConnection(url, username, password);
从上面的代码中可以明显看出,少了第一步中的类加载,也就是说肯定有逻辑帮我们实现了类加载的过程,而这个过程就是由SPI机制中的服务查找实现的!打开DriverManager类,可以看到如下一段静态代码块:
static {
//这句话很重还要,在我们直接使用DriverManager,该静态代码块会被执行
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
==========================================================》
//下面是loadInitialDrivers()方法中较为重要的方法
private static void loadInitialDrivers() {
//这一步返回了new ServiceLoader<>(service, loader);对象
//类中有个静态属性private static final String PREFIX = "meta-INF/services/";
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
//这一步返回了一个迭代器
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
//参照下面
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
===========================================================》
// driversIterator.next();
c = Class.forName(cn, false, loader);
S p = service.cast(c.newInstance());
从上面可以看到,SPI机制最终通过扫描meta-INF/services/最终同样执行了Class.forName(XXX);,这样就与前面的方法接上了。
参考文章



