【SPI】浅谈JDK中SPI技术
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
常见的 SPI 有 JDBC、日志门面接口、Spring、SpringBoot相关starter组件、Dubbo、JNDI等。
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
要使用Java SPI,需要遵循如下约定:
1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个不带参数的构造方法。
示例:
先创建四个maven项目,分别为spi-inter(定义标准服务接口)、spi-defaultInterImpl(服务提供方提供的默认实现)、spi-myInterImpl(调用方觉得默认实现不好使,自己写的实现)、spi-testSpi。
在spi-inter 中定义标准服务接口, 然后将项目打包。
1 package com.demo.spi_inter; 2 /** 3 * 服务方定义的标准服务接口 4 */ 5 public interface Config { 6 7 void loadConfig(); 8 }
在spi-defaultInterImpl的pom.xml中添加依赖spi-inter,并进行对接口的实现。在src/main/java目录下新建META-INF/services目录,并在services中新建文件,文件名为接口的全限定名,
如示例:“com.demo.spi_inter.Config”,内容为接口实现类的全限定名。如示例:”com.demo.spi_defaultInterImpl.DefaultInterImpl“。然后将项目打包。
1 package com.demo.spi_defaultInterImpl; 2 3 import com.demo.spi_inter.Config; 4 5 public class DefaultInterImpl implements Config{ 6 7 @Override 8 public void loadConfig() { 9 System.out.println("这是对接口的默认实现"); 10 } 11 12 }
在spi-testSpi的pom.xml文件中添加入spi-defaultInterImpl的依赖。使用 ServiceLoader 来加载配置文件中指定的实现。
1 package com.demo.spi_testSpi; 2 3 import java.util.Iterator; 4 import java.util.ServiceLoader; 5 6 import com.demo.spi_inter.Config; 7 8 public class App { 9 public static void main(String[] args) { 10 System.out.println("开始加载"); 11 ServiceLoader<Config> loader = ServiceLoader.load(Config.class); 12 Iterator<Config> iterator = loader.iterator(); 13 while (iterator.hasNext()) { 14 Config config = (Config) iterator.next(); 15 config.loadConfig(); 16 } 17 } 18 }
运行,可得结果:
此时,个人调用方觉得默认的实现不太好,于是自己编写了一个新的实现spi-myInterImpl。并创建了相对应的目录结构及文件/META-INF/services/com.demo.spi_inter.Config,文件内容为个人实现的全限定名。打包,将依赖引入到spi-testSpi中。
1 package com.demo.spi_myInterImpl; 2 3 import com.demo.spi_inter.Config; 4 5 public class MyInterImpl implements Config{ 6 7 @Override 8 public void loadConfig() { 9 System.out.println("这是个人写的实现"); 10 } 11 }
运行spi-testSpi,可以看见控制台输出:
SPI技术的优劣:
优点:
解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
缺点:
虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
多个并发多线程使用ServiceLoader类的实例是不安全的。