当前位置: 首页 > 科技观察

Java中经常提到的SPI到底是什么?

时间:2023-03-19 16:02:27 科技观察

Java程序员在日常工作中经常听到SPI,很多框架都使用了SPI技术,那么问题来了,什么是SPI?今天阿粉就带大家详细了解一下SPI。SPI概念SPI的全称是ServiceProviderInterface。它是一种动态加载和实现JDK内置扩展点的机制。通过SPI技术,我们无需自己创建即可动态获取接口的实现类。接口和实现类说到这里,那么SPI技术的技术细节有哪些呢?接口:需要有功能接口;实现类:接口只是一个规范,具体的实现需要一个实现类,所以实现类是必不可少的;配置文件:实现SPI机制,必须有一个与接口同名的文件存放在类路径下的META-INF/services文件夹中,文件中每一行的内容都是完整的实现类的路径;类加载器ServiceLoader:JDK内置的类加载器,用于加载配置文件中的实现类;举个栗子说说SPI的几个概念,接下来阿粉会通过一个栗子给大家展示具体的用法。第一步是创建一个接口,这里我们创建一个解压接口,里面定义了压缩和解压两种方法。packagecom.example.demo.spi;publicinterfaceCompresser{byte[]compress(byte[]bytes);byte[]decompress(byte[]bytes);}第二步再写两个对应的实际类,分别是GzipCompresser.java和WinRarCompresser.java代码如下packagecom.example.demo.spi.impl;importcom.example.demo.spi.Compresser;导入java.nio.charset.StandardCharsets;公共类GzipCompresser实现Compresser{@Overridepublicbyte[]compress(byte[]bytes){return"compressbyGzip".getBytes(StandardCharsets.UTF_8);}@Overridepublicbyte[]decompress(byte[]bytes){return"decompressbyGzip".getBytes(StandardCharsets.UTF_8);}}packagecom.example.demo.spi.impl;importcom.example.demo.spi.Compresser;importjava.nio.charset.StandardCharsets;publicclassWinRarCompresserimplementsCompresser{@Overridepublicbyte[]compress(byte[]bytes){return"由WinRar压缩".getBytes(StandardCharsets.UTF_8);}@Overridepublicbyte[]decompress(byte[]bytes){返回“WinR解压缩ar".getBytes(StandardCharsets.UTF_8);}}第三步创建配置文件,我们接着在resources目录下创建一个名为META-INF/services的文件夹,并创建一个名为com.example.demo.spi的文件夹.Compresser文件,内容如下:com.example.demo.spi.impl.WinRarCompressercom.example.demo.spi.impl.GzipCompresser注意文件名必须是接口的全路径,并且文件中的内容每一行是一个实现类的全路径,多个实现类分多行写,效果如下第四步有了上面的接口,实现类和配置文件,接下来我们就可以使用了ServiceLoader动态加载实现SPI技术的实现类,如下:packagecom.example.demo;importcom.example.demo.spi.Compresser;importjava.nio.charset.StandardCharsets;importjava.util.ServiceLoader;publicclassTestSPI{publicstaticvoidmain(String[]args){ServiceLoadercompressors=ServiceLoader.load(Compresser.class);对于(压缩机压缩机:压缩机){System.out.println(compresser.getClass());}}}运行结果如下。可以看到我们已经正常获取了接口的实现类,可以直接使用实现类的解压方法了。原理知道怎么用SPI。接下来,我们来研究如何实现它。通过上面的测试我们可以看到核心逻辑是ServiceLoader.load()方法,有点类似于Spring中根据接口获取所有的实现类。点击ServiceLoader,我们可以看到有一个常量PREFIX,如下图,这就是为什么我们必须在这个路径下创建一个配置文件,因为JDK代码会从这个路径读取我们的文件。同时,因为读取文件时使用的是类的路径名,并且因为我们在使用load方法时只传递一个类,所以我们的文件名也必须是接口的全路径。通过load方法,我们可以看到底层构造了一个java.util.ServiceLoader.LazyIterator迭代器。在iterator中的parse方法中,获取配置文件中的实现类名集合,然后通过反射创建具体的实现类对象,存储到LinkedHashMapproviders=newLinkedHashMap<>();中间。常用的框架SPI技术应用广泛,比如在Dubble中,但是Dubble中的SPI已经被修改,SPI也用在了我们很常见的数据库驱动中。有兴趣的朋友可以去翻翻,还有SLF4J用来加载不同provider和Spring框架的日志实现类。优缺点前面介绍了SPI的原理和使用,那么SPI有哪些优缺点呢?好处当然是解耦。服务器只需要定义接口规范即可。具体实现可以通过不同的jar来实现。只要按照规范实现功能,就可以直接使用。在某些情况下,它会热插拔并用于实现解耦的功能。缺点一个明显的缺点就是不能按需加载。我们从源码中可以看出,所有的实现类都会被创建。这种方法会降低性能。如果有些实现类实现起来比较耗时,会影响加载时间。同时,实现类的命名也没有规范,不方便用户参考。