1 文章概述
SPI(Service Provider Interface)是一种服务发现机制,本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件加载实现类,这样可以在运行时动态为接口替换实现类,我们通过 SPI 机制可以为程序提供拓展功能。
本文我们介绍JDK SPI使用实例并通过分析源码深入理解,后续文章会介绍Dubbo增强版本SPI机制。
2 SPI实例分析
(1) 新建DataBaseDriver工程并定义接口DataBaseDriver
public interface DataBaseDriver { String connect(String hostIp);}
(2) 打包这个工程为JAR
<dependency> <groupId>com.itxpz.spi</groupId> <artifactId>DataBaseDriver</artifactId> <version>1.0.0-SNAPSHOT</version></dependency>
(3) 新建MySQLDriver工程添加上述依赖并实现DataBaseDriver接口
import com.itxpz.database.driver.DataBaseDriver;public class MySQLDataBaseDriver implements DataBaseDriver { @Override public String connect(String hostIp) { return "MySQL DataBase Driver connect"; }}
(4) 在MySQLDriver项目新建文件
src/main/resources/META-INF/services/com.itxpz.database.driver.DataBaseDriver
(5) 在此文件添加如下内容
com.itxpz.database.mysql.driver.MySQLDataBaseDriver
(6) 新建OracleDriver工程操作方式相同,配置文件内容有所变化
com.itxpz.database.oracle.driver.OracleDataBaseDriver
(7) 将上述两个项目打包
<dependency> <groupId>com.itxpz.spi</groupId> <artifactId>MySQLDriver</artifactId> <version>1.0.0-SNAPSHOT</version></dependency><dependency> <groupId>com.itxpz.spi</groupId> <artifactId>OracleDriver</artifactId> <version>1.0.0-SNAPSHOT</version></dependency>
(8) 新建测试项目引入上述MySQLDriver、OracleDriver依赖
public class DataBaseConnector { public static void main(String[] args) { ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class); Iterator<DataBaseDriver> iterator = serviceLoader.iterator(); while (iterator.hasNext()) { DataBaseDriver driver = iterator.next(); System.out.println(driver.connect("localhost")); } }}
// 输出结果// MySQL DataBase Driver connect// Oracle DataBase Driver connect
我们并没有指定使用哪个驱动进行数据库连接,而是通过ServiceLoader方式加载所有实现了DataBaseDriver接口的实现类。假设我们只需要使用MySQL数据库驱动那么直接引入相应依赖即可。
3 源码分析
3.1 迭代器模式
在分析JDK SPI源码之前首先学习迭代器设计模式,因为JDK SPI应用了迭代器模式。
public class OrderInfoModel implements Serializable { private String orderId;
public OrderInfoModel(String orderId) { this.orderId = orderId; }
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
@Override public String toString() { return "OrderInfoModel [orderId=" + orderId + "]"; }}
import java.util.Iterator;import java.util.List;import org.springframework.util.CollectionUtils;
public class OrderInfoIterator implements Iterator<OrderInfoModel> { private int cursor; private List<OrderInfoModel> orderInfoList;
public OrderInfoIterator(List<OrderInfoModel> orderInfoList) { this.cursor = 0; this.orderInfoList = orderInfoList; }
@Override public boolean hasNext() { if(CollectionUtils.isEmpty(orderInfoList)) { throw new RuntimeException("param error"); } return cursor != orderInfoList.size(); }
@Override public OrderInfoModel next() { if(CollectionUtils.isEmpty(orderInfoList)) { throw new RuntimeException("param error"); } OrderInfoModel element = orderInfoList.get(cursor); cursor++; return element; }}
public class TestMain { public static void main(String[] args) { List<OrderInfoModel> orderInfoList = new ArrayList<>(); OrderInfoModel order1 = new OrderInfoModel("111"); OrderInfoModel order2 = new OrderInfoModel("222"); OrderInfoModel order3 = new OrderInfoModel("333"); orderInfoList.add(order1); orderInfoList.add(order2); orderInfoList.add(order3);
Iterator<OrderInfoModel> iterator = new OrderInfoIterator(orderInfoList); while (iterator.hasNext()) { System.out.println(iterator.next()); } }}
// 输出结果// OrderInfoModel [orderId=111]// OrderInfoModel [orderId=222]// OrderInfoModel [orderId=333]
3.2 SPI源码分析
进入ServiceLoader.load方法分析
ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class);
发现ServiceLoader.load方法进行初始化
public final class ServiceLoader<S> implements Iterable<S> {
// 默认加载服务路径 private static final String PREFIX = "META-INF/services/";
// 缓存提供者信息 private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
// 当前迭代器 private LazyIterator lookupIterator;
public void reload() { // 清除缓存 providers.clear(); // 核心迭代器 lookupIterator = new LazyIterator(service, loader); }
private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }}
进入serviceLoader.iterator方法分析
public Iterator<S> iterator() { return new Iterator<S>() { Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); }
public S next() { // 如果缓存有则取缓存 if (knownProviders.hasNext()) return knownProviders.next().getValue(); // 缓存没有则重新加载 return lookupIterator.next(); }
public void remove() { throw new UnsupportedOperationException(); } }}
进入迭代器遍历代码分析
while (iterator.hasNext()) { DataBaseDriver driver = iterator.next(); System.out.println(driver.connect("localhost"));}
LazyIterator核心方法分析详见注释。核心是读取指定路径文件内容,通过反射进行类实例化并且保存至缓存容器。因为创建类需要使用栈空间,如果不使用缓存频繁创建类会造成栈溢出异常。
private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null;
private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { // META-INFO/Services/com.itxpz.database.driver.DataBaseDriver String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else // 构建fullName路径配置对象 configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } // 解析文件内容 pending = parse(service, configs.nextElement()); } // com.itxpz.database.mysql.driver.MySQLDataBaseDriver // com.itxpz.database.mysql.driver.OracleDataBaseDriver nextName = pending.next(); return true; }
private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); // com.itxpz.database.mysql.driver.MySQLDataBaseDriver // com.itxpz.database.mysql.driver.OracleDataBaseDriver String cn = nextName; nextName = null; Class<?> c = null; try { // 通过反射进行实例化 c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { // 类型转换父类引用指向子类对象 S p = service.cast(c.newInstance()); // 保存至缓存容器 providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); }}
4 SPI实际应用
JDBC利用DriverManager加载驱动正是使用SPI机制,我们引入MySQL依赖
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version></dependency>
在MySQL依赖包会发现如下文件
META-INF/services/java.sql.Driver
我们分析DriverManager加载驱动代码可以发现SPI机制
package java.sql;public class DriverManager { private static void loadInitialDrivers() { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { // META-INF/services/java.sql.Driver ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try { while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { } return null; } }); }}