「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」一起交流学习

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值