java SPI机制的源码分析

🌸概述

SPI,是Service Provider Interface的简称,它是Java中常用到的一种机制,目的是为了实现功能的热插拔。像Java中的数据库驱动:java.sql.Driver就使用到了这种机制。

我们先来看看一个简单的例子来了解一下SPI
🟢第一步:创建一个文件加载的接口

public interface FileLoad {
    void loadFile(String type);
}

🟢第二步:创建一个本地文件加载的实现类

public class NativeLoad implements FileLoad{
    @Override
    public void loadFile(String type) {
        System.out.println("this is nativeLoad");
    }
}

🟢第三步:创建一个SQL文件加载的实现类

public class SqlLoad implements FileLoad{

    @Override
    public void loadFile(String type) {
        System.out.println("this is sqlLoad");
    }
}

🟢第四步:在类路径下的MATE-INF/services下创建一个文本文件,名字是接口的全限定名,里面的内容填写这个接口实现类的全限定名称。

🟢第五步:测试类

public class TestSpi {

    public static void main(String[] args) {
        ServiceLoader<FileLoad> serviceLoader = ServiceLoader.load(FileLoad.class);
        Iterator<FileLoad> iterator = serviceLoader.iterator();
        while(iterator.hasNext()){
            iterator.next().loadFile("1");
        }
    }
}

🟢第六步:控制台打印结果

this is nativeLoad
this is sqlLoad

Java通过ServiceLoader类来实现了SPI机制,通过这个例子可以看到,通过传入要使用的接口,SPI能去指定路径下找到这个接口的所有定义的实现类并使用。

下面我通过调试带大家真正的认识这个机制是如何运行的

在分析源码前,我先介绍一下ServiceLoader类,方便大家能轻松的阅读接下来的源码

🌷ServiceLoader的成员属性

public final class ServiceLoader<S>
    implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";

    // 接口的Class对象
    private final Class<S> service;

    // 类加载器,默认是AppClassLoader
    private final ClassLoader loader;

    // 访问控制上下文
    private final AccessControlContext acc;

    // 缓存接口的实现类
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 内部类懒加载Iterator对象
    private LazyIterator lookupIterator;

🟢ServiceLoader继承了Iterator,所以能使用hasNext和next等方法
🟢String PREFIX = “META-INF/services/”,从这里可以知道,SPI默认会去这个目录下找文件
🟢接口的Class对象service方便创建实现类
🟢使用了LinkedHashMap来缓存实现类,所以,不必每次都去创建接口实现类
🟢懒加载对象,用时才加载,这个用时是什么时候,大家可以先思考思考,后续会解答

🌼源码刨析

下面我们开始进入正题,源码的刨析,我们还是依据上面的例子来调试
在这里插入图片描述
ServiceLoader调用load方法进入,传入了接口的Class。

在这里插入图片描述
进入ServiceLoader的构造方法,传入了接口的Class和默认的类加载器AppClassLoader。

在这里插入图片描述
构造方法中调用的reload方法,作用是将接口的实现类缓存链表清空,同时创建一个懒加载迭代器。

到这里ServiceLoader的load方法就全部执行完毕了。它主要做的事情就是调用reload方法将缓存清空并初始化懒加载迭代器。

接下来执行ServiceLoader的Iterator方法
在这里插入图片描述
这个方法是在Iterator的基础上包裹了缓存Provider和懒加载迭代器lookupIterator,这个方法的目的是利用到缓存,当provider中没有实现类时,它会调用懒加载迭代器去创建实现类并放入到缓存中,下次使用时就能直接从缓存中取得。

接下来就是执行Iterator的方法hasNext。
在这里插入图片描述
这是第一次调用,所以缓存Provider中没有值,所以会调用懒加载迭代器的hasNext方法去创建实现类。

在这里插入图片描述
这个方法是ServiceLoader的方法,里边是调用hasNextService方法。
在这里插入图片描述
这个方法拿到接口的类路径,通过类加载器拿到这个接口的系统资源,然后调用parse方法。

在这里插入图片描述
这个方法是通过接口的字节码和该接口的绝对路径来解析指定路径下的文件。这个方法中是使用parseLine方法来逐行解析文件中实现类全限定名。

进入到parseLine方法中
在这里插入图片描述
这个方法的执行流程是:
🟢通过BufferedReader的readLine读取一行
🟢处理#号和空格并拿到这一行的有效字节数
🟢for循环遍历实现类的全限定名,检查每个字符是否符合Java命名要求
🟢校验通过后检查缓存和names列表中是否有该值,无则添加
🟢返回要处理的下一行的行数

注意⚠️:在配置文件中写了多少行,这个方法就会执行多少次。只有文件中都读取完毕,parse方法才会往下继续执行。在本例中,我们定义了两个实现类,也就是两行,所以这个方法执行了两次。

返回到parse方法中
在这里插入图片描述
解析完这个接口的指定路径下的文件中的所有内容后,ArrayList类型的name中存放了该接口的所有实现类的全限定名,都是string类型。该方法将这个集合的迭代器返回给hasNextService继续执行。

在这里插入图片描述
该方法接口返回的迭代器并返回true给hasNext方法

到此,整个hasNext方法执行完成

接下来是next方法的执行
在这里插入图片描述
和hasNext方法一样,还是一样的逻辑,判断缓存中没值,会调用懒加载迭代器执行next方法。
在这里插入图片描述
这个方法是懒加载迭代器中的方法,步骤如下:
🟢拿到当前接口要实例化的一个实现类全限定名
🟢通过Class.forName来创建这个实现类的字节码
🟢调用实现类字节码的newInstance方法,以反射的方式来实例化这个实现类
🟢将这个实现类实例以k.v形式保存到缓存Provider中,缓存是LinkedHashMap类型
🟢返回这个实现类实例
🟢通过这个实现类实例来调用指定的方法

至此,next方法也执行完毕了。
源码刨析就到这里结束啦🙂

🌺Java SPI机制的优点

🟢丝毫不会影响Java虚拟机启动时间,只有在真正要用到接口实现类时,也就是调用Iterator.next方法时才会用反射的方式创建实现类实例,这很好的体现了SPI机制的懒加载。
🟢解析文件只会发生在第一次调用,第一次调用完毕后将该接口的所有实现类缓存,后续调用将会很快。
🟢使用与否在于你是否导入实现方的依赖,如果需要使用,导入依赖即可,不适用,即不用导入依赖,自然也不会扫描到实现方META-INF/services下的文件,且结合reload方法达到了热插拔的效果。
🟢非常适合于框架开发和插件部署。

🌻Java SPI机制的缺点

🟢虽然解析只发生在调用的第一次,但是,文件的行数影响parseLIne方法的执行次数,每行的字节数影响parseLine方法中for循环的次数,文件越大,执行解析的时间越长。
🟢获取实现类实例比较死板,只能通过迭代器遍历的方式结合反射依次获得实例。且获得实例的顺序和文件中定义的顺序一致。如果用户想获得这个接口某个指定的实现类实例是不可能的,只能遍历比较。

如果大家喜欢这篇文章,那就关注加点赞👍,同时,文章的不足之处,大家可以评论区留言,谢谢🙏

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值