SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,由于这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java spi机制的思想。咱们系统里抽象的各个模块,每每有不少不一样的实现方案,好比日志模块的方案,xml解析模块、jdbc模块的方案等。面向对象的设计里,咱们通常推荐模块之间基于接口编程,模块之间不使用实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,若是须要替换一种实现,就须要修改代码。为了实如今模块装配的时候动态指定具体实现类,这就须要一种服务发现机制。 java spi就是提供这种功能的机制:为某个接口寻找服务实现的机制。有点相似IOC的思想,将装配的控制权移到程序以外,在模块化设计中这个机制尤为重要。 java
java SPI应用场景很普遍,在Java底层和一些框架中都很经常使用,好比java数据驱动加载和Dubbo。Java底层定义加载接口后,由不一样的厂商提供驱动加载的实现方式,当咱们须要加载不一样的数据库的时候,只须要替换数据库对应的驱动加载jar包,就能够进行使用。数据库
要使用Java SPI,须要遵循以下约定:编程
一、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下建立一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;api
二、接口实现类所在的jar包放在主程序的classpath中;安全
三、主程序经过java.util.ServiceLoder动态装载实现模块,它经过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;多线程
四、SPI的实现类必须携带一个不带参数的构造方法;并发
public interface SpiDemo { void say(); }
public class SpiDemoImpl1 implements SpiDemo { public void say() { System.out.println("SpiDemoImpl1"); } }
每个SPI接口都须要在本身项目的静态资源目录中声明一个services文件,文件名为实现规范接口的类名全路径。在resources目录中建立\META-INF\services目录,建立以com.hanggle.spi.api.SpiDemo为名的文件。(文件名便是要实现的接口类的全路径以下图)框架
文件内容:maven
com.hanggle.spi.api.impl1.SpiDemoImpl1
public class SpiDemoImpl2 implements SpiDemo { public void say() { System.out.println("SpiDemoImpl2"); } }
同spi-demo-impl1同样ide
在resources目录中建立\META-INF\services目录,建立以com.hanggle.spi.api.SpiDemo为名的文件。
文件内容:
com.hanggle.spi.api.impl1.SpiDemoImpl2
public static void main(String[] args) { ServiceLoader<SpiDemo> serviceLoader = ServiceLoader.load(SpiDemo.class); for (SpiDemo o : serviceLoader) { o.say(); } }
运行结果:
装配文件路径的定义:
有源代码能够,java会根据定义的路径去扫描可能存在的接口的实现。放在config中,而后使用parse方法将配置文件中的接口实现全路径放在pending中,并取得第一个实现类(变量nextName),
而后使用类加载器加载,加载须要调用的类,而后调用实现的方法
优势:
使用Java SPI机制的优点是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一块儿。应用程序能够根据实际业务状况启用框架扩展或替换框架组件。
缺点:
虽然ServiceLoader也算是使用的延迟加载,可是基本只能经过遍历所有获取,也就是接口的实现类所有加载并实例化一遍。若是你并不想用某些实现类,它也被加载并实例化了,这就形成了浪费。获取某个实现类的方式不够灵活,只能经过Iterator形式获取,不能根据某个参数来获取对应的实现类。
多个并发多线程使用ServiceLoader类的实例是不安全的。
参考:http://www.pandan.xyz/2017/03/14/java%20SPI%E6%9C%BA%E5%88%B6%E5%8E%9F%E7%90%86/