「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」一塊兒交流學習

相關文章
相關標籤/搜索