「DUBBO系列」JDK SPI机制原理
欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,欢迎大家加我微信「java_front」一起交流学习
1 文章概述
SPI(Service Provider Interface)是一种服务发现机制,本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件加载实现类,这样可以在运行时动态为接口替换实现类,我们通过 SPI 机制可以为程序提供拓展功能。本文我们介绍JDK SPI使用方法并通过分析源码深入理解。后续文章介绍Dubbo自己实现的SPI机制。
2 SPI实例
(1) 新建项目工程并定义接口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) 新建测试项目引入上述依赖并执行以下代码
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」一起交流学习
还没有评论,来说两句吧...