「DUBBO系列」JDK SPI机制_java

 

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;            }        });    }}

 

「DUBBO系列」JDK SPI机制_java _02