Java SPI 浅析一二

Java SPI

SPI : Service Provider Interface 为某个接口寻找服务实现的机制。

1. 为何需要SPI

基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

Usually API and SPI are separate. For example, in JDBC the Driver class is part of the SPI: If you simply want to use JDBC, you don’t need to use it directly, but everyone who implements a JDBC driver must implement that class.

2. 默认约定

当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。通过这个约定,就不需要把服务放在代码中了,通过模块被装配的时候就可以发现服务类了。

3. 使用案例

3.1 common-logging

common-logging apache最早提供的日志的门面接口。只有接口,没有实现。具体方案由各提供商实现, 发现日志提供商是通过扫描 META-INF/services/org.apache.commons.logging.LogFactory配置文件,来发现日志实现类。只要我们的日志实现里包含了这个文件,并在文件里指定了 LogFactory工厂接口的实现类即可。

3.2 Spring

在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。

3.3 JDBC

在JDBC4.0之前,我们开发有连接数据库的时候,通常会用Class.forName(“com.mysql.jdbc.Driver”)这句先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0之后不需要用Class.forName(“com.mysql.jdbc.Driver”)来加载驱动,直接获取连接就可以了,现在这种方式就是使用了Java的SPI扩展机制来实现。

  • JDBC接口定义
    首先在java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商来提供的。

  • mysql实现
    在mysql的jar包中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。

  • postgresql实现
    同样在postgresql的jar包中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是postgresql对Java的java.sql.Driver的实现。

3.4 Eclipse插件

Eclipse使用OSGi作为插件系统的基础,动态添加新插件和停止现有插件,以动态的方式管理组件生命周期。 一般来说,插件的文件结构必须在指定目录下包含以下三个文件: META-INF/MANIFEST.MF: 项目基本配置信息,版本、名称、启动器等
build.properties: 项目的编译配置信息,包括,源代码路径、输出路径
plugin.xml:插件的操作配置信息,包含弹出菜单及点击菜单后对应的操作执行类等.

4. 自己用ServiceLoader实现SPI

定义一个接口和相关的实现类

public interface Save {
    void save(String message);
}

public class FileSaver implements Save{
    @Override
    public void save(String message) {
        System.out.println("saveit in file");
    }
}

public class DBSaver implements Save{
    @Override
    public void save(String message) {
        System.out.println("saveit in DB");
    }
}

在Resource目录下建目录 META-INF/services/ 和文件 com.spi.Save文件。注意文件名就是接口的全路径名称。
在这里插入图片描述
然后在文件内写明实现类。

com.spi.DBSaver
com.spi.FileSaver

这样子在Main里实现接口实现类的自动查找与实例化。

public class Main {
    public static void main(String[] args) {
        ServiceLoader<Save> load = ServiceLoader.load(Save.class);
        Iterator<Save> iterator = load.iterator();
        while (iterator.hasNext()){
            Save save = iterator.next();
            save.save("Hello");
        }
    }
}

运行结果:

saveit in DB
saveit in file

5. ServiceLoader源码

ServiceLoader类内的迭代器有一个LazyIterator,其中会根据文本加载类:
c = Class.forName(cn, false, loader);并且实例化:S p = service.cast(c.newInstance());

  private class LazyIterator
        implements Iterator<S>
    {
  private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }
}

总结

SPI将装配的控制权移到了程序之外。
只要是能满足用户按照系统规则来自定义,并且可以注册到系统中的功能点,都带有着spi的思想。

java基础 系列在github上有一个开源项目,主要是本系列博客的demo代码。https://github.com/forestnlp/javabasic
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。
您的支持是对我最大的鼓励。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟空学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值