JDK SPI的实践与原理分析

1. SPI是什么?

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。主要是被框架开发人员使用的一种技术。例如,使用 Java 语言访问数据库时我们会使用到 java.sql.Driver 接口,不同数据库产品底层的协议不同,提供的 java.sql.Driver 实现也不同,在开发 java.sql.Driver 接口时,开发人员并不清楚用户最终会使用哪个数据库,在这种情况下就可以使用 Java SPI 机制在实际运行过程中,为 java.sql.Driver 接口寻找具体的实现。

2. JDK SPI的使用

首先定义一个接口在这里插入代码片:

public interface Log {
    void log(String message);
}

然后定义多个实现接口:

public class MyLogA implements Log{
    @Override
    public void log(String message) {
        System.out.println("MyLogA - log: " + message);
    }
}

public class MyLogB implements Log{
    @Override
    public void log(String message) {
        System.out.println("MyLogB - log: " + message);
    }
}

在项目的 resources/META-INF/services 目录下添加一个名为 com.tdt.spi.Log的文件(没有什么后缀名),这是 JDK SPI 需要读取的配置文件,具体内容就是接口实现类的全路径,如下:

com.tdt.spi.MyLogA
com.tdt.spi.MyLogB

最后创建 main() 方法,加载上述配置文件并创建全部 Log 接口实现的实例,并执行其 log() 方法,如下所示:

public static void main(String[] args) {
        ServiceLoader<Log> logServiceLoader = ServiceLoader.load(Log.class);
        Iterator<Log> iterator = logServiceLoader.iterator();
        while (iterator.hasNext()) {
            Log log = iterator.next();
            log.log(" execute log spi");
        }
    }

代码结构图如下:在这里插入图片描述

3. 原理分析

我们可以看到 JDK SPI 的入口方法是 ServiceLoader.load() 方法,下面我们进入源码,

public static <S> ServiceLoader<S> load(Class<S> service) {
	// 当前线程类加载器	
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
 }

private ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl) {
       // 省略
       ......

       this.service = svc;
       this.serviceName = svc.getName();
       this.layer = null;
       this.loader = cl;
       this.acc = (System.getSecurityManager() != null)
               ? AccessController.getContext()
               : null;
   } 

service : 表示正在加载的服务的接口
serviceName : 接口的名称
layer : 类加载器的提供者,初始化时为null
loader : 用于查找,加载和实例化提供程序的类加载器
acc : 创建ServiceLoader时采取的访问控制上下文

由上可以看到ServiceLoader构造函数为私有,只能通过静态方法进行实例化,有单例模式的影子,实例化ServiceLoader只对参数进行校验,设置一些基本属性的值,大部分实现逻辑在迭代器里面。

接着分析ServiceLoader遍历:

public Iterator<S> iterator() {
        // create lookup iterator if needed
        if (lookupIterator1 == null) {
        	// 创建迭代器
            lookupIterator1 = newLookupIterator();
        }

        return new Iterator<S>() {
            // record reload count
            final int expectedReloadCount = ServiceLoader.this.reloadCount;

            // index into the cached providers list
            int index;

            /**
             * Throws ConcurrentModificationException if the list of cached
             * providers has been cleared by reload.
             */
            private void checkReloadCount() {
                if (ServiceLoader.this.reloadCount != expectedReloadCount)
                    throw new ConcurrentModificationException();
            }

            @Override
            public boolean hasNext() {
                checkReloadCount();
                if (index < instantiatedProviders.size())
                    return true;
                return lookupIterator1.hasNext();
            }

            @Override
            public S next() {
                checkReloadCount();
                S next;
                if (index < instantiatedProviders.size()) {
                    next = instantiatedProviders.get(index);
                } else {
                    next = lookupIterator1.next().get();
                    instantiatedProviders.add(next);
                }
                index++;
                return next;
            }

        };
    }

创建真实的迭代器:

private Iterator<Provider<S>> newLookupIterator() {
        assert layer == null || loader == null;
        if (layer != null) {
            return new LayerLookupIterator<>();
        } else {
            Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
            Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
            return new Iterator<Provider<S>>() {
                @Override
                public boolean hasNext() {
                    return (first.hasNext() || second.hasNext());
                }
                @Override
                public Provider<S> next() {
                    if (first.hasNext()) {
                        return first.next();
                    } else if (second.hasNext()) {
                        return second.next();
                    } else {
                        throw new NoSuchElementException();
                    }
                }
            };
        }
    }

从上面可以看出来ServiceLoad获取的迭代器是创建的一个匿名迭代器,同时该匿名迭代器再指向一个匿名迭代器,同时该迭代器包含真正执行hasNext和next方法进行遍历.
由LazyClassPathLookupIterator加载META-INF/services/指定文件的内容

迭代器之间的关系大概为:在这里插入图片描述

4. jdk spi在jdbc中的运用

JDK 中只定义了一个 java.sql.Driver 接口,具体的实现是由不同数据库厂商来提供的。以 MySQL 提供的 JDBC 实现包为例进行分析。在 mysql-connector-java-*.jar 包中的 META-INF/services 目录下,有一个 java.sql.Driver 文件中只有一行内容,如下所示:
com.mysql.cj.jdbc.Driver

在使用 mysql-connector-java-*.jar 包连接 MySQL 数据库的时候,我们会用到如下语句创建数据库连接:

javaString url = "jdbc:xxx://xxx:xxx/xxx"; 
Connection conn = DriverManager.getConnection(url, username, pwd); 

DriverManager 是 JDK 提供的数据库驱动管理器,在调用 getConnection() 方法的时候,DriverManager 类会被 Java 虚拟机加载、解析并触发 static 代码块的执行;在 loadInitialDrivers() 方法中通过 JDK SPI 扫描 Classpath 下 java.sql.Driver 接口实现类并实例化,核心实现如下所示:

private static void loadInitialDrivers() { 
    String drivers = System.getProperty("jdbc.drivers") 
    // 使用 JDK SPI机制加载所有 java.sql.Driver实现类 
    ServiceLoader<Driver> loadedDrivers =  
           ServiceLoader.load(Driver.class); 
    Iterator<Driver> driversIterator = loadedDrivers.iterator(); 
    while(driversIterator.hasNext()) { 
        driversIterator.next(); 
    } 
    String[] driversList = drivers.split(":"); 
    for (String aDriver : driversList) { // 初始化Driver实现类 
        Class.forName(aDriver, true, 
            ClassLoader.getSystemClassLoader()); 
    } 
} 

在 MySQL 提供的 com.mysql.cj.jdbc.Driver 实现类中,同样有一段 static 静态代码块,这段代码会创建一个 com.mysql.cj.jdbc.Driver 对象并注册到 DriverManager.registeredDrivers 集合中(CopyOnWriteArrayList 类型),如下所示:

static { 
   java.sql.DriverManager.registerDriver(new Driver()); 
} 

在 getConnection() 方法中,DriverManager 从该 registeredDrivers 集合中获取对应的 Driver 对象创建 Connection.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值