「DUBBO系列」JDK SPI机制原理

系统管理员 2021-09-24 23:36 390阅读 0赞

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

1 文章概述

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

2 SPI实例

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

  1. public interface DataBaseDriver {
  2. String connect(String hostIp);
  3. }

(2) 打包这个工程为JAR

  1. <dependency>
  2. <groupId>com.itxpz.spi</groupId>
  3. <artifactId>DataBaseDriver</artifactId>
  4. <version>1.0.0-SNAPSHOT</version>
  5. </dependency>

(3) 新建MySQLDriver工程添加上述依赖并实现DataBaseDriver接口

  1. import com.itxpz.database.driver.DataBaseDriver;
  2. public class MySQLDataBaseDriver implements DataBaseDriver {
  3. @Override
  4. public String connect(String hostIp) {
  5. return "MySQL DataBase Driver connect";
  6. }
  7. }

(4) 在MySQLDriver项目新建文件

  1. src/main/resources/META-INF/services/com.itxpz.database.driver.DataBaseDriver

(5) 在此文件添加如下内容

  1. com.itxpz.database.mysql.driver.MySQLDataBaseDriver

(6) 新建OracleDriver工程操作方式相同,配置文件内容有所变化

  1. com.itxpz.database.oracle.driver.OracleDataBaseDriver

(7) 将上述两个项目打包

  1. <dependency>
  2. <groupId>com.itxpz.spi</groupId>
  3. <artifactId>MySQLDriver</artifactId>
  4. <version>1.0.0-SNAPSHOT</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.itxpz.spi</groupId>
  8. <artifactId>OracleDriver</artifactId>
  9. <version>1.0.0-SNAPSHOT</version>
  10. </dependency>

(8) 新建测试项目引入上述依赖并执行以下代码

  1. public class DataBaseConnector {
  2. public static void main(String[] args) {
  3. ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class);
  4. Iterator<DataBaseDriver> iterator = serviceLoader.iterator();
  5. while (iterator.hasNext()) {
  6. DataBaseDriver driver = iterator.next();
  7. System.out.println(driver.connect("localhost"));
  8. }
  9. }
  10. }
  11. 输出结果
  12. MySQL DataBase Driver connect
  13. Oracle DataBase Driver connect

我们并没有指定使用哪个驱动进行连接,而是通过ServiceLoader方式加载实现了DataBaseDriver接口的实现类。假设我们只想要使用MySQL驱动那么直接引入相应依赖即可。

3 源码分析

3.1 迭代器模式

我们在分析JDK SPI源码之前首先学习迭代器设计模式,因为JDK SPI应用了迭代器模式。

  1. public class OrderInfoModel implements Serializable {
  2. private String orderId;
  3. public OrderInfoModel(String orderId) {
  4. this.orderId = orderId;
  5. }
  6. public String getOrderId() {
  7. return orderId;
  8. }
  9. public void setOrderId(String orderId) {
  10. this.orderId = orderId;
  11. }
  12. @Override
  13. public String toString() {
  14. return "OrderInfoModel [orderId=" + orderId + "]";
  15. }
  16. }
  17. public class OrderInfoIterator implements Iterator<OrderInfoModel> {
  18. private int cursor;
  19. private List<OrderInfoModel> orderInfoList;
  20. public OrderInfoIterator(List<OrderInfoModel> orderInfoList) {
  21. this.cursor = 0;
  22. this.orderInfoList = orderInfoList;
  23. }
  24. @Override
  25. public boolean hasNext() {
  26. if(CollectionUtils.isEmpty(orderInfoList)) {
  27. throw new RuntimeException("param error");
  28. }
  29. return cursor != orderInfoList.size();
  30. }
  31. @Override
  32. public OrderInfoModel next() {
  33. if(CollectionUtils.isEmpty(orderInfoList)) {
  34. throw new RuntimeException("param error");
  35. }
  36. OrderInfoModel element = orderInfoList.get(cursor);
  37. cursor++;
  38. return element;
  39. }
  40. }
  41. public class TestMain {
  42. public static void main(String[] args) {
  43. List<OrderInfoModel> orderInfoList = new ArrayList<>();
  44. OrderInfoModel order1 = new OrderInfoModel("111");
  45. OrderInfoModel order2 = new OrderInfoModel("222");
  46. OrderInfoModel order3 = new OrderInfoModel("333");
  47. orderInfoList.add(order1);
  48. orderInfoList.add(order2);
  49. orderInfoList.add(order3);
  50. Iterator<OrderInfoModel> iterator = new OrderInfoIterator(orderInfoList);
  51. while (iterator.hasNext()) {
  52. System.out.println(iterator.next());
  53. }
  54. }
  55. }
  56. 输出结果
  57. OrderInfoModel [orderId=111]
  58. OrderInfoModel [orderId=222]
  59. OrderInfoModel [orderId=333]

3.2 SPI源码分析

  1. public class DataBaseConnector {
  2. public static void main(String[] args) {
  3. // 根据类型获取服务加载器
  4. ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class);
  5. // 获取迭代器
  6. Iterator<DataBaseDriver> iterator = serviceLoader.iterator();
  7. // 迭代器遍历
  8. while (iterator.hasNext()) {
  9. DataBaseDriver driver = iterator.next();
  10. System.out.println(driver.connect("localhost"));
  11. }
  12. }
  13. }

进入ServiceLoader.load方法

  1. ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class);

跟进load方法发现只是进行初始化

  1. public final class ServiceLoader<S> implements Iterable<S> {
  2. // 默认加载服务路径
  3. private static final String PREFIX = "META-INF/services/";
  4. // 缓存提供者信息
  5. private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
  6. // 当前迭代器
  7. private LazyIterator lookupIterator;
  8. public void reload() {
  9. // 清除缓存
  10. providers.clear();
  11. // 核心迭代器
  12. lookupIterator = new LazyIterator(service, loader);
  13. }
  14. private ServiceLoader(Class<S> svc, ClassLoader cl) {
  15. service = Objects.requireNonNull(svc, "Service interface cannot be null");
  16. loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
  17. acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
  18. reload();
  19. }
  20. }

进入serviceLoader.iterator()方法

  1. public Iterator<S> iterator() {
  2. return new Iterator<S>() {
  3. Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
  4. public boolean hasNext() {
  5. if (knownProviders.hasNext())
  6. return true;
  7. return lookupIterator.hasNext();
  8. }
  9. public S next() {
  10. // 如果缓存有则取缓存
  11. if (knownProviders.hasNext())
  12. return knownProviders.next().getValue();
  13. // 缓存没有则重新加载
  14. return lookupIterator.next();
  15. }
  16. public void remove() {
  17. throw new UnsupportedOperationException();
  18. }
  19. }
  20. }

进入迭代器遍历代码

  1. while (iterator.hasNext()) {
  2. DataBaseDriver driver = iterator.next();
  3. System.out.println(driver.connect("localhost"));
  4. }

LazyIterator核心方法分析详见注释。核心是读取指定路径文件内容,通过反射进行类实例化并且保存至缓存容器。因为创建类需要使用栈空间,如果不使用缓存频繁创建类会造成栈溢出异常。

  1. private class LazyIterator implements Iterator<S> {
  2. Class<S> service;
  3. ClassLoader loader;
  4. Enumeration<URL> configs = null;
  5. Iterator<String> pending = null;
  6. String nextName = null;
  7. private boolean hasNextService() {
  8. if (nextName != null) {
  9. return true;
  10. }
  11. if (configs == null) {
  12. try {
  13. // META-INFO/Services/com.itxpz.database.driver.DataBaseDriver
  14. String fullName = PREFIX + service.getName();
  15. if (loader == null)
  16. configs = ClassLoader.getSystemResources(fullName);
  17. else
  18. // 构建fullName路径配置对象
  19. configs = loader.getResources(fullName);
  20. } catch (IOException x) {
  21. fail(service, "Error locating configuration files", x);
  22. }
  23. }
  24. while ((pending == null) || !pending.hasNext()) {
  25. if (!configs.hasMoreElements()) {
  26. return false;
  27. }
  28. // 解析文件内容
  29. pending = parse(service, configs.nextElement());
  30. }
  31. // com.itxpz.database.mysql.driver.MySQLDataBaseDriver
  32. // com.itxpz.database.mysql.driver.OracleDataBaseDriver
  33. nextName = pending.next();
  34. return true;
  35. }
  36. private S nextService() {
  37. if (!hasNextService())
  38. throw new NoSuchElementException();
  39. // com.itxpz.database.mysql.driver.MySQLDataBaseDriver
  40. // com.itxpz.database.mysql.driver.OracleDataBaseDriver
  41. String cn = nextName;
  42. nextName = null;
  43. Class<?> c = null;
  44. try {
  45. // 通过反射进行实例化
  46. c = Class.forName(cn, false, loader);
  47. } catch (ClassNotFoundException x) {
  48. fail(service, "Provider " + cn + " not found");
  49. }
  50. if (!service.isAssignableFrom(c)) {
  51. fail(service, "Provider " + cn + " not a subtype");
  52. }
  53. try {
  54. // 类型转换父类引用指向子类对象
  55. S p = service.cast(c.newInstance());
  56. // 保存至缓存容器
  57. providers.put(cn, p);
  58. return p;
  59. } catch (Throwable x) {
  60. fail(service, "Provider " + cn + " could not be instantiated", x);
  61. }
  62. throw new Error();
  63. }
  64. }

4 实际应用

使用JDBC时利用DriverManager加载数据库驱动时正是使用了SPI机制,我们引入MySQL依赖

  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. <version>6.0.6</version>
  5. </dependency>

在MySQL依赖包中会发现如下文件

  1. META-INF/services/java.sql.Driver

DriverManager加载驱动时可以发现SPI机制

  1. package java.sql;
  2. public class DriverManager {
  3. private static void loadInitialDrivers() {
  4. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  5. public Void run() {
  6. // META-INF/services/java.sql.Driver
  7. ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
  8. Iterator<Driver> driversIterator = loadedDrivers.iterator();
  9. try {
  10. while(driversIterator.hasNext()) {
  11. driversIterator.next();
  12. }
  13. } catch(Throwable t) {
  14. }
  15. return null;
  16. }
  17. });
  18. }
  19. }

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

发表评论

表情:
评论列表 (有 0 条评论,390人围观)

还没有评论,来说两句吧...

相关阅读

    相关 jdkdubboSPI机制

    [jdk和dubbo的SPI机制][jdk_dubbo_SPI] 前言:开闭原则一直是软件开发领域中所追求的,开闭原则中的"开"是指对于组件功能的扩展是开放的,是允许对其