什么是SPIService provider interface (SPI) is an API intended to be implemented or extended by a third party. It can be used to enable framework extension and replaceable components.直译过去SPI是一个能被第三方继承或者实现的API,个别用于框架扩张或者可变组件。可能这个了解起来还是比拟难,再找一个图来看看

看起来如同比较简单一点了,然而对于我在刚接触SPI的时候还是比拟难懂,所以我画了一个我感觉比拟适宜老手同学的图

能够看到我用3个色彩来标注3个解决接口方,惯例状况下,这3方个别是隶属于3个组织,或者1个组织的3个部门,转化成具体的代码就是这3方个别是属于3个工程的。SPI实际有了以上的基础知识,接下来再用一个例子来加深大家的理解,大体的步骤如下:定义一个Interface标准有两个工程实现这个Interface再定义一个应用Interface的工程具体的工程目录如下:

接口定义方整个模块啥也不干,单纯地定义一个接口

接口实现方接口实现方如果想实现下面定义的接口,第一步首先必定是在pom文件中引入java-api-service 这个模块

接着也是啥也不干,单纯地实现定义的接口即可,上面是接口实现方1的代码

为了大家看得更分明一点,我特意在一个接口实现方中定义了两个具体实现类XxlServiceLoader

XxlOtherServiceLoader

接口实现方2的代码也是相似的

CuteyServiceLoader

留神:这两个接口实现方是附属不同module,然而独特依赖了接口定义方留神:这两个接口实现方是附属不同module,然而独特依赖了接口定义方留神:这两个接口实现方是附属不同module,然而独特依赖了接口定义方然而实现了接口还不行,要想实现SPI还得有一个公共的约定配置文件,在META-INF目录下新建一个services目录,有些工程创立的时候可能没有META-INF,那就得在resource目录下先新建META-INF。接着在services目录中新增一个文本文件,文件名为接口定义的全限定类名,比方下面的例子实现的接口是InterfaceService ,那文本文件名称就必须是com.cutey.none.spi.service.InterfaceService 。留神,这里的文本文件并不是指文件格式txt,而是单纯文本文件。文本文件的内容为具体实现类的全限定类名,针对下面的例子,不同实现的文本文件内容如下:接口实现方1com.cutey.none.spi.serviceloader.XxlOtherServiceLoadercom.cutey.none.spi.serviceloader.XxlServiceLoader接口实现方2com.cutey.none.spi.serviceloader.CuteyServiceLoader理论文件目录及内容如图

接口应用方有了以上接口定义和接口实现,接下来看看应用方是怎么应用的,首先应用方要应用这个接口,那必定也是要依赖接口定义方,接着要应用具体的实现,那同样也须要依赖接口实现方。

整个工程是比较简单的,外面只有一个相似Main的办法

App类外面的内容如下,类外面的读取SPI是用java.util.ServiceLoader

看下输入是什么

能够看到在应用方中没有写任何接口实现的代码,仅仅是导入了2个依赖jar,就能应用依赖jar包中的具体实现,感兴趣的同学能够试试如果只依赖上述的独自1个jar会不会是你想的预期。工程中的SPI下面用一个简略的例子帮大家疾速体验了下什么是SPI,当初再来简略看看身边有哪些应用SPI的例子。由wikipedia能够看到有这些应用了SPI,接下来咱们挑耳熟能详的数据库和spring.factories来看看。

数据库略微相熟一点的同学都晓得以前是应用Class.forName("com.mysql.cj.jdbc.Driver"); 去加载驱动类的。对于mysql而言mysql8更改了驱动类的包门路,而后引入了mysql8后发现代码跑不通了还专门去网上搜为什么。所以这种形式必定是有弊病的,浅举3个须要手动编写驱动类地址,不论是硬编码还是配置文件的模式,都须要咱们在应用的时候编写驱动类全限定类名更改驱动类不不便,须要找到定义的中央,而后再去替换当类名发生变化后须要应用方去适配,如果存在信息差那会导致程序运行不了而在有了spi后,曾经不必以上形式去加载驱动类了,而是应用DriverManager.getConnection() 去连贯数据库,能够看到,连驱动类是啥都曾经不关注了。当然,无论哪种模式必定都要援用数据库的jar包,这是大前提。因为这里不是讲SPI的原理,就不深究DriverManager外面干了啥,这里就贴一张图表明确实用到了spi,感兴趣的同学能够自行钻研源码。

为了让各位同学更简略直白的看到这个成果,咱新建一个工程,而后pom文件中引入mysql和oracle的依赖,编写个main办法去加载,看能不能取到两个数据库驱动类的全限定类名。工程目录如下

好不客气地说是啥都没有,而后减少Main类中的办法

接着打断点进行代码调试,看下providerNames是不是真的有咱们引入的依赖jar包了。

具体怎么做到的呢,在最开始spi实际咱们本人实现的时候就晓得是在src/main/resources/META-INF/services 目录下有个以接口作为文件名的文本文件,那当初就去mysql和oracle中的jar包中验证下。

能够看到的确如此,看到这里就能领会到一个最最最显著的益处,那就是哪怕当前mysql的接口实现类产生了变动,咱们也不必关注,因为在它自身的jar包中就提供了驱动实现类的全限定类名。spring.factories写在后面,spring.factories是SpringBoot的个性,而非是Spring,而后是用于主动拆卸(目前我理解到的是只用于这个)。所谓主动拆卸,说得再简略,那就是在不侵入式批改代码的状况下可能加载其余jar包内的bean。上面演示代码的整体概要:有两个模块,java-spi-springboot-provider 和 java-spi-springboot前者蕴含Student的bean,后着蕴含Teacher的beanjava-spi-springboot要应用java-spi-springboot-provider 里的bean两个bean所在的包门路不一样,Student的全限定类名为com.xxl.cutey.none.javaspispringbootprovider.Student ,Teacher的全限定类名为com.cutey.none.xxl.javaspispringboot.Teacher java-spi-springboot依赖java-spi-springboot-provider整体的工程目录图如下

各位小伙伴先别想着spring.factories,就失常状况下,咱们是怎么应用别的jar包外面的bean的。也就是在非provider中应用Student这个bean,那首先必是要先加@Component 注解。java-spi-springboot-provider新建一个Student类,而后在主启动类中读取这个bean

主启动类

留神,在主启动类和bean不在同一个包门路下的时候,是须要用scanBasePackage扫包能力把bean注入到IOC容器中。接下来看下输入,没问题。

java-spi-springboot新建一个Teacher和主启动类都和下面一样就不再赘述,当初是想用下面申明的bean,那就只有一个办法,批改scanBasePackages,要么是扫两个Bean的公共包,还记得吗,Student和Teacher我特意把全限定类名改得不懂;要么是具体写完这两个Bean的全限定类名。

看下输入,留神:如果Student的包名没写对,那必定是获取不到Student的bean的,因为它是在别的jar包上面。


问题来了,如果我想要再引别的组织的包呢,要晓得在实在开发中,不同部门,不同组织,甚至不同公司之间互相援用切实是太失常了,难道每次援用一个都得这么加吗,这就引出了spring.factories。咱们革新的是java-spi-springboot-provider

接下来咱们再批改下java-spi-springboot中的包,把扫描的student的包去掉

咱们能够神奇的发现,还是能读取进去bean

让咱们浅浅看Spring用是不是真的用到了SPI

诶,如同没看到跟数据库一样用到了jdk中的ServiceLoader呀,是不是骗你们了捏,其实不是,其实SpringFactoriesLoader就是类是jdk中的ServiceLoader。说白了,spring应用的SPI的思维,而SPI精确来说也不是一个接口,而是面向接口编程的其中一种思维,是接口应用方和接口定义方约定一种面向编程的思维。参考文献Service provider interface - Wikipedia10分钟让你彻底明确Java SPI,附实例代码演示#安员外很有码哔哩哔哩bilibiliJava SPI (Service Provider Interface) 机制详解 - 腾讯云开发者社区-腾讯云 (tencent.com)