SPI服务扩展机制

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值