SPI机制深入分析

概要


SPI全称是Service Provider Interface. 大多数开发人员可能不熟悉,因为这个机制是针对第三方厂商或者插件来提供服务的。SPI机制是jdk中java.util.ServiceLoader提供的,后面我们会详细介绍。

一般我们在做开发的时候,都是抽象各个模块,然后有很多的实现方案,比如日志模块、xml解析模块、jdbc模块等。在面向对象设计里,我们一般推荐模块之间最好是基于接口编程,而不对具体的实现进行硬编码。因为一旦代码改动涉及到具体的实现类,就违反了可插拔的原则。为了实现模块装配的时候不在程序里动态指明,就需要一种服务发现机制。而Java SPI就提供了这样的一种机制:为某个接口寻找服务实现的机制。这个有点类似与IOC的思想,就是将装配的控制权移交到程序之外

什么是SPI


SPI是JDK内置的一种服务提供发现机制。目前市面上有很多框架都是用它来做服务的扩展发现。简单来说,它是一种动态替换发现的机制。举个简单的例子,我们想在运行时动态给它添加实现,你只需要添加一个实现,然后把新的实现描述给JDK知道就行了。

实现SPI需要遵循的标准
我们如何去实现一个标准的SPI发现机制呢?其实很简单,只需要满足以下提交就行了

需要在classpath下创建一个目录,该目录命名必须是:META-INF/service
在该目录下创建一个properties文件,该文件需要满足以下几个条件 
2.1 文件名必须是扩展的接口的全路径名称 
2.2 文件内部描述的是该扩展接口的所有实现类 
2.3 文件的编码格式是UTF-8
通过java.util.ServiceLoader的加载机制来发现

代码演示

代码结构

è¿éåå¾çæè¿°

代码说明

我们定义了一个接口叫IHelloService, 接口中有一个方法

 public interface IHelloService {
    void sayHello();
}

接着定义了两个实现类,一个是通过文本的方式来问候,另一个是通过表情的方式来问候,我们来看一下这两个实现类的代码:

public class FaceHelloServiceImpl implements IHelloService{
    public void sayHello() {
        System.out.println("say hello with face:^_^");
    }
}
public class TextHelloServiceImp implements IHelloService{

    public void sayHello() {
        System.out.println("say hello with text:你好");
    }
}

以上就是核心代码了,接着来看一下resources目录下的文件,按照上面第二点说的要实现SPI需要满足的格式: 
1. resources/META-INF/service。 
2. 文件名要以扩展接口的全路径名:com.gupao.spi.demo.hello.IHelloService.properties 
3. 该文件的内容如下
 

com.gupao.spi.demo.hello.impl.FaceHelloServiceImpl
com.gupao.spi.demo.hello.impl.TextHelloServiceImp

最后就是调用的代码了

public class SPIMain
{
    public static void main(String[] args) {
        ServiceLoader<IHelloService> loader=ServiceLoader.load(IHelloService.class);
        for (IHelloService hi:loader){
            hi.sayHello();
        }
    }
}

通过SPIMain方法,就可以分别输出

say hello with face:^_^
say hello with text:你好

这样就实现了一个简单的SPI的实现

SPI的实际应用


其实SPI在很多地方有应用,可能大家都没有关注,最常用的就是JDBC驱动,我们来看看是怎么应用的 
JDK本身提供了数据访问的api。在java.sql这个包里面 
我们在连接数据库的时候,一定需要用到java.sql.Driver这个接口对吧。然后我好奇的去看了下java.sql.Driver的源码,发现Driver并没有实现,而是提供了一套标准的api接口。大家有兴趣可以去看看 
因为我们在实际应用中用的比较多的是mysql,所以我去mysql的包里面看到一个如下的目录结构 
è¿éåå¾çæè¿°

看到了吗? 目录结构是不是符合SPI的定义?我怀着好奇心,打开了java.sql.Driver这个文件

è¿éåå¾çæè¿°

这个文件里面写的就是mysql的驱动实现。我恍然大悟,原来通过SPI机制把java.sql.Driver和mysql的驱动做了集成。这样就达到了各个数据库厂商自己去实现数据库连接,jdk本身不关心你怎么实现。

SPI的缺点
JDK标准的SPI会一次性加载实例化扩展点的所有实现,什么意思呢?就是如果你在META-INF/service下的文件里面加了N个实现类,那么JDK启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到,那么会很浪费资源
如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因


转自:https://blog.csdn.net/k1280000/article/details/65648279

猜你喜欢

转载自blog.csdn.net/FENGQIYUNRAN/article/details/83965231