java SPI机制

什么是SPI    

    软件很大程度是真实世界的反映,思想、模式都能在真实事物找到影子。这不难理解,因为不管是真实事物还是软件都是由人类创造,都是朝着高效、低成本方向发展,难免相互借鉴。SPI也是如此,SPI全称Service Provider Interface。本质就是上层软件制定所需模块的接口规范,然后基于这个规范搭建上层建筑,至于模块谁来开发实现并不关心,反正厂商按照接口规范做出来就好。好比汽车厂商开发一款新型汽车,轮胎不要他生产,他只要告诉轮胎厂商这款车要用195/55/R16 85V 规格的轮胎,轮胎厂商按照这个标准生产即可。

    SPI的应用比较常见,比如说 (1) jdbc驱动,如果针对具体的数据库开发软件,换数据库将是灾难级调整。所以业务面向jdbc接口规范开发,利用SPI自动发现接口实现类将极大提高软件的扩展性;(2) 之前写过的ServletContainerInitializer,servlet容器用SPI发现spring-web包中的SpringServletContainerInitializer从而启动初始化应用;(3) 此外,还有日志框架、validation(hibernator-validator)也是基于SPI机制发现具体的服务实现。

SPI机制原理

    SPI的核心是java.util.ServiceLoader。ServiceLoader大致的过程是在classpath*:META-INF/services/目录下查找配置文件(名称是接口的全路径类名),这个文件里面每一行就是实现类的全路径类名,然后用当前线程的classloader加载实现类。具体实现可以看看jdk源码:

public final class ServiceLoader<S> implements Iterable<S>{
    //查找的目录
    private static final String PREFIX = "META-INF/services/";
    ......

    //内部类 具体的查找的实现
    private class LazyIterator implements Iterator<S>{
        ......
        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
        ......
    }
    ......
}

spi demo程序

    整一个demo程序跑一跑,否则太干巴巴的了,demo程序尽量简单,只是打个日志,实现2个具体的实现类。

    首先,定一个接口LogPrint

public interface LogPrint {
    //打印日志
    void  print(String message);
}

   其次,创建2个实现类LogPrintImplA、LogPrintImplB

package com.focuse.jdkdemo.spi;

public class LogPrintImplA implements LogPrint {
    @Override
    public void print(String message) {
        System.out.println("LogPrinterA: " + message);
    }
}
package com.focuse.jdkdemo.spi;

public class LogPrintImplB implements LogPrint {
    @Override
    public void print(String message) {
        System.out.println("LogPrinterB: " + message);
    }
}

第三步,在目录META-INF/services下创建文件com.focuse.jdkdemo.spi.LogPrint,文件内容2行。

最后,用ServiceLoader加载实现类,并运行程序

package com.focuse.jdkdemo.spi;

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

/**
 * @author :focuse
 * @date :Created in 2020/2/16 上午11:10
 * @description:
 * @modified By:
 */
public class SPIDemo {

    public static void main(String[] args) {
        ServiceLoader<LogPrint> loader = ServiceLoader.load(LogPrint.class);
        Iterator<LogPrint> it = loader.iterator();
        while (it.hasNext()) {
            it.next().print("hello spi!");
        }
    }
}

ps: 为了让demo程序简单,就没有用jar引用的形式,而是直接在一个工程里面完成所有的事情

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章