歡迎你們關注公衆號「JAVA前線」查看更多精彩分享文章,主要包括源碼分析、實際應用、架構思惟、職場分享、產品思考等等,同時歡迎你們加我微信「java_front」一塊兒交流學習java
SPI(Service Provider Interface)是一種服務發現機制,本質是將接口實現類的全限定名配置在文件中,並由服務加載器讀取配置文件加載實現類,這樣能夠在運行時動態爲接口替換實現類,咱們經過 SPI 機制能夠爲程序提供拓展功能。本文咱們介紹JDK SPI使用方法並經過分析源碼深刻理解。後續文章介紹Dubbo本身實現的SPI機制。mysql
(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驅動那麼直接引入相應依賴便可。
咱們在分析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]
複製代碼
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();
}
}
複製代碼
使用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」一塊兒交流學習