![77a4281560a715a8e3dabf430a278123.png](https://i-blog.csdnimg.cn/blog_migrate/99b2bbc6b166ade16425645b4d41cf49.jpeg)
一、简介
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,SPI的本质是将接口实现类的全限定名配置在文件中,并由服务的加载器读取配置文件信息,加载实现类。这样就可以在程序运行过程中动态为接口替换或新增实现类。
Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。
二、SPI与API区别
都是实现接口,可能很多同学会将这两个概念混淆,通俗的讲:API 和 SPI 都是相对的概念,他们的差别只在语义上,API 直接被应用开发人员使用,SPI 被框架扩展人员使用。
API (Application Programming Interface)
一般来讲API都是接口实现方实现来定制接口的实现,调用方调用接口但不能修改接口的实现,如下图:
![0900183ec8ca6928a88a0fc76206182b.png](https://i-blog.csdnimg.cn/blog_migrate/2174a0e217ebc535797138a8a527673c.jpeg)
SPI(Service Provider Interface)
SPI是一种回调的思想。回调是指在使用api时,可以向api传入一个类或者方法,api在合适的时间调用类或者方法。
![ef2aecd7522c5a4ec089fa33caa1e446.png](https://i-blog.csdnimg.cn/blog_migrate/d63034b2c48cdc53f99b12da3a491ecc.jpeg)
三、SPI机制的约定
java spi的具体约定为:
(1) 当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。
(2) 外部程序使用ServiceLoader类动态加载META-INF中的实现类。
![0dad845d1bcb8434aee49929621bf455.png](https://i-blog.csdnimg.cn/blog_migrate/6cc5ffa27813af7e42fb710e81092229.jpeg)
四、SPI使用场景
(1) spring中的应用
Spring中就有类似的SPI机制, 通过SpringFactoriesLoader代替JDK中的ServiceLoader, 通过META-INF/spring.factories文件代替META-INF/service目录下的描述文件,就是使用了JDK的SPI机制。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static List loadFactories(Class factoryClass, ClassLoader classLoader) { Assert.notNull(factoryClass, "'factoryClass' must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoader == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } List factoryNames = loadFactoryNames(factoryClass, classLoaderToUse); .................}
spring.factories文件是以Java的Properties格式存在,key是接口或抽象类的全名、value是以逗号 “ , “ 分隔的实现类,比如:
org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
(2) mysql-connector-java.jar
驱动管理器中提供了两种默认加载驱动的方法,一个就是通过虚拟机参数jdbc.drivers来加载驱动。另一个是通过java的spi接口加载驱动。第一种方法比较简单就不用探讨了,第二种方法我们来具体了解一下。
![3eb14ea07bb88e3e5fd56b9e63051c19.png](https://i-blog.csdnimg.cn/blog_migrate/042a085162dc08ff29c541957e85b057.jpeg)
在mysql的连接包中我们可以看到一个META-INF/services/java.sql.Driver,通过SPI机制动态加载jdbc驱动。
com.mysql.jdbc.Drivercom.mysql.fabric.jdbc.FabricMySQLDriver
五、SPI示例
1.我们定义一个sayHello()接口:
public interface Hello { void sayHello();}
2.定义一个实现类实现sayHello()接口:
public class HelloIm implements Hello { @Override public void sayHello() { System.out.println("Hello World。"); }}
3.META-INF/services文件夹下创建一个文件,名称为Hello:
com.wxt.spi.HelloIm
4.完成上述步骤后我们编写测试类:
public class JavaSPITest { @Test public void testSPI(){ ServiceLoader helloServiceLoader=ServiceLoader.load(Hello.class); helloServiceLoader.forEach(Hello::sayHello); }}
六、SPI机制缺点
(1) 不能按需加载。虽然 ServiceLoader 做了延迟载入,但是基本只能通过遍历全部获取,也就是接口的实现类得全部载入并实例化一遍。
(2) 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
(3) 多个并发多线程使用 ServiceLoader 类的实例是不安全的。
(4) 加载不到实现类时抛出并不是真正原因的异常,错误很难定位。
七、了解dubbo的spi机制
针对SPI的上述缺点,很多系统都自己实现了SPI机制例如我们常使用的分布式框架dubbo。
(1) Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。
(2) 与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。
(3) Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性。