SPI服务扩展机制

  • Post author:
  • Post category:其他


SPI服务扩展机制

SPI全称为service-provider,中文意思是服务扩展机制。

SPI:是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

也可以这样理解:

SPI是“基于接口的编程+策略模式+配置文件”组成实现的动态加载机制。

可能比较抽象,等看完就能理解了。

使用到SPI的例子:

  • Servlet规范

    Servlet3.0规范启动ServletContainerInitializer启动流程,在Servlet中就使用到了SPI,servlet-api Jar只是定义了一些规范。服务的具体实现由具体的Web容器(Tomcat / Jboss / Jetty)去实现。
  • JDBC规范

    JDBC规范也是只定义了一些接口和类,具体服务的实现由具体的数据库厂商(mysql驱动 / Oracle驱动)去实现。


使用SPI的机制的要求(重点):


1. 当服务提供者提供了接口的一种具体实现后,在工程中的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名。

2. 接口实现类所在的工程classpath中:

3. 主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名。

4. SPI的实现类必须具有一个不带参数的构造方法

下面举个例子:

定义了一个文件上传的接口

package com.zlin.spi;

/**
 * SPI
 * 上传文件接口  binary name
 */
public interface IUpload {
    void upload();
}

然后Img PDF Txt文件的实现类都重写upload方法

package com.zlin.spi;

public class ImgUpload implements IUpload{
    @Override
    public void upload() {
        System.out.println("上传图片");
    }
}

package com.zlin.spi;

public class PdfUpload implements IUpload{
    @Override
    public void upload() {
        System.out.println("上传PDF");
    }
}

package com.zlin.spi;

public class TxtUpload implements IUpload{
    @Override
    public void upload() {
        System.out.println("上传txt文件");
    }
}

然后在工程中的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名。

在这里插入图片描述

再通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名。

先看效果

在这里插入图片描述

去查看ServiceLoader的源码会发现,

ServiceLoader<IUpload> iUploads = ServiceLoader.load(IUpload.class);

会去获取线程上下文类加载器,然后把我们的接口类型保存到ServiceLoader变量中,最后创建一个懒加载的迭代器。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

返回我们懒加载的迭代器对象

Iterator<IUpload> iUploadIterator = iUploads.iterator();


iUploadIterator.hasNext():会读取”META-INF/services/com.zlin.spi.IUpload”的内容,然后把文件中的第一个值(第一个实现类的全限定名)保存到nextName变量。


第二次把第二个值保存到newxtName变量(第二个实现类的全限定名)

依次下去…

iUploadIterator.hasNext():


iUploadIterator.next() 得到的是接口的实现类的实例, 会调用 c = Class.forName(cn, false, loader);

iUploadIterator.next().upload();

在这里插入图片描述

hasNext()和next()的源码这里就不贴上来了,因为很简单也很好理解。

要是真的想学习的小伙伴,建议去自己动手敲一遍,进到hasNext()和next()里面打断点,整个流程走一遍,就全都懂了。

package com.zlin.spi;

import java.util.Iterator;
import java.util.ServiceLoader;

public class SpiTest {
    public static void main(String[] args) {
        testSpi();
    }

    private static void testSpi() {
        // 第一步:把我们的接口类型保存到ServiceLoader变量中
        // 第二步:创建一个我们 懒加载的迭代器
        // 1.保存了IUpload接口到service   2.保存线程上下文类加载器(AppClassLoader)  3.创建一个懒加载的迭代器LazyIterator
        ServiceLoader<IUpload> iUploads = ServiceLoader.load(IUpload.class);

        // 返回我们懒加载的迭代器对象
        Iterator<IUpload> iUploadIterator = iUploads.iterator();

        // iUploadIterator.hasNext(): 读取"META-INF/services/接口全类名"的内容
        // 第一次把"META-INF/services/接口全类名"文件中的第一个值保存到newxtName变量  (第一个实现类的全限定名)
        // 第二次把第二个值保存到newxtName变量(第二个实现类的全限定名)   依次下去...
        while (iUploadIterator.hasNext()) {
            // iUploadIterator.next() 得到的是接口的实现类的实例
            // iUploadIterator.next() 会调用  c = Class.forName(cn, false, loader);
            // 会触发类的初始化 ==> 调用类的静态代码块    初始化只会触发一次
            iUploadIterator.next().upload();
        }
    }
}

SPI在mysql-connector-java中的使用:

在这里插入图片描述

上面涉及到的代码地址:


https://github.com/zhonglinliu123/MyBlogCode/tree/master/java/SPI



版权声明:本文为weixin_43082343原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。