当前位置: 首页 > 后端技术 > Java

面向接口编程?什么是服务提供者接口?什么是界面

时间:2023-04-02 09:34:03 Java

?什么是SPI?服务提供商接口(SPI)是旨在由第三方实施或扩展的API。它可用于启用框架扩展和可替换组件。直译过来,SPI是第三方继承或实现的API,一般用于框架扩展或可变组件。也许这仍然很难理解。再找一张图看好像比较简单,但是对于刚接触SPI的我来说还是比较难理解,所以画了一张我觉得比较适合新手同学的图。大家可以看到我用了3种颜色来标示3个处理接口方。一般情况下,这3个当事人一般隶属于3个组织,或1个组织的3个部门。换算成具体代码,这3方一般属于3个项目。有了以上SPI实践中的基础知识,下面我们通过一个例子来加深大家的理解。大致步骤如下:定义一个Interface规范,有两个项目实现这个Interface,然后定义一个使用Interface的项目。具体工程目录如下:interfacedefiner对整个模块什么都不做,只是定义了一个interface接口实现器。接口实现者如果要实现上面定义的接口,第一步必须是在pom文件中引入java-api-service模块。然后呢?不做,只实现定义的接口。下面是接口实现器1的代码,为了让大家看的更清楚,我特意在一个接口实现器中定义了两个具体的实现类XxlServiceLoaderXxlOtherServiceLoader接口实现器2。代码也类似CuteyServiceLoader注:这两个接口实现器属于不同的模块,但是都依赖接口定义器注:这两个接口实现器属于不同的模块,但是都依赖接口定义器注:这两个接口实现器属于不同的模块,但是都依赖于接口定义器,但是仅仅实现接口是不够的。要实现SPI,必须要有一个公共的协议配置文件。在META-INF目录下创建一个服务目录。有些项目创建的时候可能没有META-INF,那么首先要在资源目录下新建一个META-INF。然后在services目录下新建一个文本文件,文件名是接口定义的全限定类名,比如上面例子实现的接口是InterfaceService,那么文本文件的名字一定是com.cutey.none.spi.service.InterfaceService。注意,这里的文本文件不是指文件格式txt,而是单纯的文本文件。文本文件的内容是具体实现类的全限定类名。对于上面的例子,不同实现的文本文件内容如下:Interfaceimplementer1com.cutey.none.spi.serviceloader.XxlOtherServiceLoadercom.cutey.none.spi.serviceloader。XxlServiceLoader接口实现者2com.cutey.none.spi.serviceloader.CuteyServiceLoader实际文件目录和内容如接口用户所示。有了上面的接口定义和接口实现,我们来看看用户是如何使用的。首先,用户要使用这个接口,也必须依赖于接口定义者,然后再使用具体的实现,也依赖于接口实现者。整个工程比较简单,只有一个类似Main的方法。App类的内容如下。使用java.util.ServiceLoader读取类中的SPI以查看输出是什么。可以看到user里面没有写接口实现的代码,只导入了2个依赖jar,可以使用依赖jar包中的具体实现。有兴趣的同学可以试试看,如果只依赖上述的单个jar,是不是你所期待的。项目中的SPI通过一个简单的例子帮助大家快速体验什么是SPI。下面简单介绍一下身边使用SPI的例子。从wikipedia中,我们可以看到这些使用了SPI。接下来,让我们看看熟悉的数据库和spring.factories。稍微熟悉数据库的同学都知道,以前都是用Class.forName("com.mysql.cj.jdbc.Driver");加载驱动程序类。对于mysql,mysql8更改了驱动类的包路径,然后引入mysql8后,发现代码运行不了,于是上网搜索原因。因此,这种方法肯定有缺点。提三,需要手动写驱动类地址。无论是硬编码还是以配置文件的形式,我们在使用时都需要写入驱动类的全限定类名。更换驱动类不方便,需要找到定义定义的地方,然后替换掉。当类名发生变化时,用户需要对其进行适配。如果存在信息差异,将导致程序无法运行。有了spi之后就不用再按上面的方式加载驱动类了,而是使用DriverManager.getConnection()连接数据库,可以看到连驱动类都不再关心了。当然无论是哪种形式,都要引用数据库的jar包,这是大前提。由于这里不涉及SPI的原理,所以就不深究DriverManager到底做了什么。这是一个清楚地适用于spi的图表。有兴趣的同学可以自行研究源码。为了让同学们更容易看到这个效果,我们新建一个项目,然后在pom文件中引入mysql和oracle的依赖,写一个main方法加载,看看能不能得到完整的limits两个数据库驱动类的类名。工程目录如下,直白的说什么都没有,然后在Main类中添加方法然后断点调试代码,看providerNames是否真的有我们引入的依赖jar包。具体怎么做呢?我们刚开始练习spi,自己实现的时候,知道在src/main/resources/META-INF/services目录下有一个以interface为文件名的文本文件。现在去mysql用oracle中的jar包验证一下。可以看出确实是这样,而且看到这里可以实现一个最明显的好处,就是以后即使mysql的接口实现类发生变化,我们也不需要关注,因为它在自己的jar包中提供了驱动实现类的全限定类名。spring.factories写在前面,spring.factories是SpringBoot的一个特性,不是Spring的,然后是用来自动组装的(目前我的理解只是用来做这个)。所谓自动组装,简单来说就是能够在不侵入修改代码的情况下加载其他jar包中的bean。下面演示代码的整体概要:有两个模块,java-spi-springboot-provider和java-spi-springboot。前者包含Student的bean,后者包含Teacher的bean。java-spi-springboot使用java-spi-springboot-provider两个bean在bean中的包路径是不同的。Student的完全限定类名是com.xxl.cutey.none.javaspispringbootprovider.Student,Teacher的完全限定类名是com.cutey.none.xxl.javaspispringboot.Teacherjava-spi-springboot依赖java-spi-springboot-provider整体工程目录图如下,小伙伴们别想spring.factories,正常情况下我们怎么使用其他jar包里的bean。也就是说如果在非provider中使用Studentbean,必须先加上@Component注解。java-spi-springboot-provider新建一个Student类,然后在主启动类中读取bean。注意当主启动类和bean不在同一个包路径下时,需要使用scanBasePackage来扫描包。该bean被注入到IOC容器中。接下来看输出,没问题。java-spi-springboot新建Teacher,主要启动类同上,不再赘述。现在我要使用上面声明的bean,那么只有一种方法,修改scanBasePackages,或者扫描两个Bean的publicpackage,或者记住,我特意改了Student和Teacher的全限定类名来理解;或者我专门写了这两个Bean的全限定类名。看输出,注意:如果Student包名没有写对,那么一定是获取不到Studentbean,因为它在另一个jar包下。问题来了,如果要引用其他组织的包,要知道在实际开发中,不同部门、不同组织、甚至不同公司相互引用是很正常的。我必须互相引用吗?像这样添加它,这导致了spring.factories。我们改造的是java-spi-springboot-provider。接下来我们修改java-spi-springboot中的包,去掉扫描出来的学生包。我们可以神奇地发现该bean仍然可以读取。让我们仔细看看Spring真的使用SPI吗?好像没看到jdk中的ServiceLoader跟数据库一样用的。这是对你的谎言吗?其实不然。其实SpringFactoriesLoader就是jdk中的ServiceLoader。说白了就是spring使用的SPI的思想,而SPI不完全是接口,而是面向接口编程的思想之一,是接口使用者和接口定义约定好的面向编程的思想派对。参考资料Serviceproviderinterface-维基百科10分钟让你全面了解JavaSPI,附实例代码演示#安少外有一个码哔哩哔哩bilibiliJavaSPI(ServiceProviderInterface)机制详解-腾讯云开发者社区-腾讯云(腾讯网)