「DUBBO系列」JDK SPI机制原理

欢迎你们关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思惟、职场分享、产品思考等等,同时欢迎你们加我微信「java_front」一块儿交流学习java


1 文章概述

SPI(Service Provider Interface)是一种服务发现机制,本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件加载实现类,这样能够在运行时动态为接口替换实现类,咱们经过 SPI 机制能够为程序提供拓展功能。本文咱们介绍JDK SPI使用方法并经过分析源码深刻理解。后续文章介绍Dubbo本身实现的SPI机制。mysql


2 SPI实例

(1) 新建项目工程并定义接口DataBaseDriversql

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工程操做方式相同,配置文件内容有所变化markdown

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) 新建测试项目引入上述依赖并执行如下代码oracle

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 + "]";
    }
}

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源码分析

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"));
        }
    }
}
复制代码

进入ServiceLoader.load方法

ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class);
复制代码

跟进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 实际应用

使用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;
            }
        });
    }
}
复制代码

欢迎你们关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思惟、职场分享、产品思考等等,同时欢迎你们加我微信「java_front」一块儿交流学习

相关文章
相关标签/搜索