乐趣区

关于java:面向接口编程Service-Provider-Interface是什么Interface

什么是 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)

退出移动版