1。简介接口是行为的抽象,是接口的实现者和调用者之间建立的契约(协议)。我们可以根据需要为接口提供不同的实现。通过向调用者注入不同的接口实现,可以在不修改调用者代码的情况下改变调用者的行为。SPI(ServiceProviderInterface)即服务提供者接口模式。框架或系统可以支持SPI模式,使用户可以根据需要为某种行为选择不同的provider实现版本,实现灵活可定制的扩展。比如JavaSDKJDBC定义了数据库操作的标准接口,但是JavaSDK并没有提供具体的实现,而是由各个数据库厂商实现的。用户可以根据自己的需要选择相应数据库的JDBC实现。2.SPI模式2.1模式包含的组件服务提供者接口模式中有4个重要的组件:服务接口ServiceInterface服务接口实现:不同的服务提供者可以提供一种或多种实现;框架或系统本身也可以提供默认实现。提供者注册API(ProviderRegistrationAPI),提供者用来注册实现;服务访问API(ServiceAccessAPI),是调用者用来获取服务实例的接口。2.2简单示例例如,假设我们有一个软件产品。该产品需要实现一项新功能以支持从其他内容系统检索信息。为了能够支持任何可能的内容系统,我们决定使用SPI模式来解决这个问题。Java提供SPI支持,我们的示例将基于JavaSPI。2.2.1定义服务接口首先,我们需要定义服务接口包com.examples.spipublicinterfaceSearchable{publicListsearchDoc(Stringkeyword);}2.2.2提供者实现服务接口然后,服务提供者实现接口。在我们的内容搜索服务中,我们可以为客户提供实施。如果客户有开发能力,可以为自己的内容系统提供搜索实现。我们许多客户使用的内容系统是同一台计算机上的文件系统。对于这样的客户,我们提供了一个支持文件系统的Searchable实现:在文件系统中搜索包含{}的内容,关键字);...//省略具体实现返回内容;}}除了使用文件内容系统的客户,其他客户使用数据库内容系统。为此,我们提供了一个支持数据库系统的Searchable实现:packagecom.examples.spi.impl.db;publicclassDatabaseSearchEngineimplementsSearchable{@OverridepublicListsearchDoc(Stringkeyword){log.info("在数据库系统中搜索包含{}的内容",keyword);...//省略具体实现返回内容;}}2.2.3注册服务提供者实现完成后,我们需要通过注册接口将实现注册到系统中。JavaSPI提供了一种注册机制。该机制是通过配置文件:META-INF/services/interfacefullyqualifiedname实现服务注册。即在使用该服务的代码工程中创建META-INF/services/目录,然后新建一个服务实现注册文件,名称为com.examples.spi.Searchable,接口全限定名。如果我们在这个服务实现注册文件中配置com.examples.spi.impl.file.FileSearchEngine,系统就会使用FileSearchEngine;如果我们在这个服务实现注册文件中配置com.examples.spi.impl.db.DatabaseSearchEngine,那么系统就会使用DatabaseSearchEngine。2.2.4通过服务访问接口调用JavaSPI的服务访问接口时的ServiceLoader。ServiceLoader可以从服务实现注册文件中加载服务实现,并返回服务对象实例。下面用一个简单的例子来看看如何获??取服务实现对象并调用服务接口。在此示例中,我们采用支持多个服务实现的方法。因此,如果在服务实现注册表文件中配置了多个实现,则可以从多个内容系统搜索内容。publicclassSeachableTest{publicstaticvoidmain(String[]args){ServiceLoaders=ServiceLoader.load(Searchable.class);迭代器<搜索>iterator=s.iterator();while(iterator.hasNext()){搜索search=iterator.next();search.searchDoc("你好世界");}}}如果你属于公司的基础架构组,负责提供公司范围内的基础架构和基础架构组件,你的组提供的框架和组件都使用log4j来写日志。但是,有的业务开发团队用log4j,有的用logback,有的用JUL(JavaUtilLogging)。不使用log4j的开发团队必须为项目维护两个日志记录配置文件。2.3例子:JDBC在JDBC4.0之前,我们在开发连接数据库时,通常会用Class.forName("com.mysql.jdbc.Driver")这句话先加载数据库相关驱动,然后获取连接,等操作。JDBC4.0以后就不需要再用Class.forName("com.mysql.jdbc.Driver")加载驱动了,直接获取连接即可。现在这个方法是使用JavaSPI扩展机制来实现的。2.3.1JDBC服务接口定义接口java.sql.Driver定义在JavaSDK中,没有具体实现,由不同的数据库厂商提供。2.3.2实现mysql在mysql的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名为java.sql.Driver的文件,文件的内容是com.mysql.cj.jdbc.Driver,这里的内容是Java定义的接口的实现。postgresql的实现也在postgresqljar包postgresql-42.0.0.jar中,同样可以找到相同的配置文件。文件内容为org.postgresql.Driver,是postgresql对Java的java.sql.Driver的实现。如上服务访问接口中提到的,现在使用SPI扩展来加载具体的驱动程序。当我们用Java编写连接数据库的代码时,不需要使用Class.forName("com.mysql.jdbc.Driver")来加载驱动。而是直接使用如下代码:Stringurl="jdbc:xxxx://xxxx:xxxx/xxxx";Connectionconn=DriverManager.getConnection(url,username,password);.....由于JDBC服务接口涉及内容比较多,所以JavaSDK提供了服务访问接口的封装DriverManager。ServiceLoader.load在DriverManager.loadInitialDrivers方法中被调用:privatestaticvoidloadInitialDrivers(){Stringdrivers;...AccessController.doPrivileged(newPrivilegedAction(){publicVoidrun(){//使用SPIServiceLoader加载接口实现ServiceLoaderloadedDrivers=ServiceLoader.load(Driver.class);IteratordriversIterator=loadedDrivers.iterator();try{//遍历所有驱动实现,遍历时,首先调用`driversIterator.hasNext()`方法,//在所有`META-INF/services中查找`java.sql.Driver`文件`类路径和jar包中的目录,在文件中找到实现类的名字...}});println("DriverManager.initialize:jdbc.drivers="+drivers);...}我们现在用的比较多的是Spring和ORM(MyBatis),所以基本上很少和JDBC交互交个朋友,这里仅以SPI为例说明JDBC是经典SPI模式的应用。2.4例子:slf4j日志框架3小结我们日常使用的组件/框架还有很多使用了SPI,比如slf4j、common-logging、Springboot的自动组装插件机制等等。当某个服务行为需要第三方或用户根据场景进行适配或扩展时,可以使用SPI方式实现灵活的自定义扩展。