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

说起Java的SPI机制,你会知道吗?

时间:2023-03-19 22:39:47 科技观察

vaSPI是一种基于接口编程+策略模式+约定配置文件相结合的动态加载机制,可以很方便的为一个接口找到服务实现机制。今天的文章将深入谈谈SPI。什么是SPI?SPI的全称:ServiceProviderInterface,是Java提供的一组接口,供第三方实现或扩展。它可用于启用框架扩展和替换组件。在面向对象设计中,我们一般推荐模块之间基于接口编程,模块之间不要硬编码实现类。一旦代码涉及到具体的实现类,就违反了可插拔性原则。如果需要更换一个实现,就需要修改代码。为了实现模块组装时不需要在程序中动态指定,这就需要服务发现机制。Javaspi提供了这样一种机制:一种为接口寻找服务实现的机制。这有点类似于IOC的思想,将汇编的控制权移出程序。这是JDK内置的一种服务发现机制,用来制定一些规范,而实际的实现方法则交给不同的服务厂商。如下图所示:解耦、可插拔、面向接口编程、动态类加载。服务提供者在提供接口实现时,需要在classpath下的META-INF/services/目录下创建一个以服务接口命名的文件。这个文件里面的内容就是这个接口的具体实现类。当其他程序需要该服务时,可以在jar包的META-INF/services/中找到配置文件(通常jar包作为依赖),配置文件中包含接口的具体实现类名.可以根据这个类名来加载实例化,服务就可以使用了。JDK中查找服务实现的工具类是:java.util.ServiceLoader。SPI的缺点是不能按需加载。需要遍历所有的实现并实例化,然后在循环中找到我们需要的实现。如果不想用某个实现类,或者某个类实例化比较耗时,也加载实例化,造成浪费。获取某个实现类的方式不够灵活,只能通过Iterator的形式获取,无法根据某个参数获取对应的实现类。(Spring的BeanFactory,ApplicationContext更高级。)多个并发多线程使用ServiceLoader类的实例是不安全的。API和SPI有什么区别?API是对类、接口、方法等为实现目标而被调用和使用的描述;SPI是对类、接口、方法等进行扩展和实现以实现目标的描述;也就是说,API提供了具体的类、方法,SPI通过操作来符合具体的类和方法。SPI和API的使用场景分析:API(ApplicationProgrammingInterface)大多数情况下,实现者制定接口并完成接口的实现。调用者仅依赖接口调用,无权选择不同的实现。从用户的角度来看,API是应用程序开发人员直接使用的。SPI(ServiceProviderInterface)是为调用者制定接口规范,对外提供实现。调用者在调用时选择自己需要的外部实现。在用户方面,SPI被框架扩展者使用。SPI案例实现下面是一个简单的案例实现:比如每个动物都有不同的叫声,因为声纹系统会定义一个接口,如下:publicinterfaceAnimalSay{voidsay();}本系统没有具体实现实现,但是在处理业务逻辑的时候需要用到这个实例。这时候需要使用SPI来加载实现类,定义一个AnimalManagerLoader。实现如下:@DatapublicclassAnimalManagerLoader{privatestaticfinalAnimalManagerLoaderINSTANCE=newAnimalManagerLoader();私人最终名单animalSays;privateAnimalManagerLoader(){animalSays=load();}/***通过SPI加载实现类*/privateListload(){ArrayListanimalSays=newArrayList<>();Iteratoriterator=ServiceLoader.load(AnimalSay.class).iterator();while(iterator.hasNext()){animalSays.add(iterator.next());}返回动物说;}publicstaticAnimalManagerLoadergetInstance(){返回实例;}}此时可以使用AnimalManagerLoader中的load方法加载对应的实现类,封装到List集合中,调用如下:publicstaticvoidmain(String[]args){AnimalManagerLoaderanimalManagerLoader=AnimalManagerLoader.getInstance();ListanimalSays=animalManagerLoader.getAnimalSays();对于(AnimalSayanimalSay:animalSays){animalSay.say();例如,狗的声纹制造商实现如下:}}catCatSay的声纹如下:/***一只猫的声纹*/publicclassCatSayimplementsAnimalSay{@Overridepublicvoidsay(){System.out.println("喵喵~");}}实现类定义后,只需要在/META-INF/services中定义一个com.myjszl.animal.api.AnimalSay文件,内容如下:com.myjszl.dog.api.DogSaycom.myjszl。dog.api.CatSaySPI应用场景SPI扩展机制应用场景很多,比如Common-Logging、JDBC、Dubbo、ShardingSphere等。1、JDBC场景java中定义的java.sql.Driver接口没有具体实现,实现方法交给不同的服务商:在MySQLjar包mysql-connector-java-6.0.6.jar中,你可以找到META-INF/services目录,在这个目录下会有一个名为java.sql.Driver的文件,文件内容为com.mysql.cj.jdbc.Driver,这里的内容是针对中定义的接口Java实现。在PostgreSQLjar包PostgreSQL-42.0.0.jar中也可以找到相同的配置文件。文件内容为org.postgresql.Driver,是PostgreSQL对Java.sql.Driver的实现。2.ShardingSphere的场景在ShardingSphere中,为了实现分布式事务,提供了一个接口ShardingTransactionManager,但是在其架构中并没有实现,而是交给了不同的厂商来实现,比如JTA强一致性事务XAShardingTransactionManager,其中在META-INF/services中有一个org.apache.shardingsphere.transaction.spi.ShardingTransactionManager文件,如下图:上面只是简单的列举了几个场景,实际的应用场景还有很多,比如在Spring和SpringBoot中都对SPI设计很有用。3.Spring场景Spring大量使用了SPI;例如:servlet3.0规范的ServletContainerInitializer的实现,自动类型转换TypeConversionSPI(ConverterSPI,FormatterSPI)等4.SLFJ日志门面场景SLF4J从不同的provider加载日志实现类,比如log4j,log4j2,logback.....总结Java的SPI机制可以很方便的实现可插拔解耦的功能设计,在日常开发中一定要想到并灵活使用这种机制。