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

深入剖析SpringBoot的SPI机制

时间:2023-04-02 01:49:07 Java

大家好,我是陈谋~SPI(ServiceProviderInterface)是JDK内置的一种服务提供者发现机制,可以用来启用框架扩展和替换组件,主要针对在框架中开发,如Dubbo、Spring、Common-Logging、JDBC等,采用SPI机制,对同一个接口使用不同的实现,提供给不同的用户,从而提高框架的扩展性。之前也写过深入分析JavaSPI:浅谈JavaSPI机制推荐Java工程师技术指南:https://github.com/chenjiabin...JavaSPI通过java.util实现Java内置的SPI.ServiceLoader类解析classPath和jar包META-INF/services/目录下以接口全限定名命名的文件,加载文件中指定的接口实现类完成调用。实例说明创建动态接口publicinterfaceVedioSPI{voidcall();}implementclass1publicclassMp3VedioimplementsVedioSPI{@Overridepublicvoidcall(){System.out.println("thisismp3call");}}实现类2public类Mp4Vedio实现VedioSPI{@Overridepublicvoidcall(){System.out.println("这是mp4调用");}}在项目源码目录下新建META-INF/services/目录,并创建com.skywares。fw.juc.spi.VedioSPI文件。相关测试serviceLoader.forEach(t->{t.call();});}}说明:Java实现spi通过ServiceLoader找到服务提供的工具类。运行结果:源码分析以上只是一个实现java内置SPI功能的简单例子。其实现原理是ServiceLoader是Java内置的查找服务提供接口的工具类。查找服务提供接口是通过调用load()方法实现的,最后遍历一个一个访问服务提供接口的实现类。从源码中可以发现,ServiceLoader类本身实现了Iterable接口,并在其中实现了iterator方法。迭代器方法的实现调用内部类LazyIterator中的方法,迭代器创建一个实例。所有提供服务的接口对应的文件都放在META-INF/services/目录下,最终的类型决定了PREFIX目录不能更改。java提供的SPI机制的思想虽然很好,但是也有相应的缺点。具体如下:Java的内置方法只能通过遍历获取。服务提供者接口必须放在META-INF/services/目录下。针对java的spi存在的问题,Spring的SPI机制沿用了SPI的思想,只是对其进行了扩展和优化。Java工程师推荐技术指南:https://github.com/chenjiabin...SpringSPISpringSPI沿袭了JavaSPI的设计思想,Spring使用spring.factories实现SPI机制,无需实现即可修改Spring源码,提供Spring框架的可扩展性。Spring实例定义接口publicinterfaceDataBaseSPI{voidgetConnection();}相关实现##DB2实现publicclassDB2DataBaseimplementsDataBaseSPI{@OverridepublicvoidgetConnection(){System.out.println("这个数据库是db2");}}##Mysql实现公共类MysqlDataBase实现DataBaseSPI{@OverridepublicvoidgetConnection(){System.out.println("这是mysql数据库");}}1.在项目的META-INF目录下,添加spring.factories文件2.填写相关接口信息,内容如下:com.skywares.fw.juc.springspi.DataBaseSPI=com.skywares。fw.juc.springspi.DB2DataBase,com.skywares.fw.juc.springspi.MysqlDataBase描述了多个实现,用逗号隔开。相关测试类publicclassSpringSPITest{publicstaticvoidmain(String[]args){ListdataBaseSPIs=SpringFactoriesLoader.loadFactories(DataBaseSPI.class,Thread.currentThread().getContextClassLoader());for(DataBaseSPIdatBaseSPI:dataBaseSPIs){datBaseSPI.getConnection();}}}输出结果从例子中可以看出,Spring使用spring.factories实现SPI和java实现SPI非常相似,只是spring的spi方法针对java的spi进行了优化。具体内容如下:JavaSPI是一个配置文件对应的服务提供者接口。当前接口的所有实现类都保存在配置文件中。多个服务提供者接口对应多个配置文件。所有配置都在services目录下;SpringfactorysSPI是一个spring.factories的配置文件,存储了多个接口和对应的实现类。接口的完全限定名用作键,实现类配置为值。多个实现类用逗号隔开,只有一个配置文件spring.factories。那么spring是如何通过加载spring.factories来实现SpI的呢?我们可以通过源码进一步分析。Java工程师推荐技术指南:https://github.com/chenjiabin...源码分析说明:loadFactoryNames解析spring.factories文件中指定接口的实现类的全限定名。具体实现如下:说明:获取所有jar包的META-INF/spring.factories文件路径,以枚举值返回。遍历spring.factories文件路径,一一加载分析,整合factoryClass类型的实现类名,获取实现类的全类名,然后进行类实例操作。相关源码如下:注:实例化是通过反射初始化实现的。