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

JavaSPI和API,愚蠢地混淆?

时间:2023-03-15 14:48:07 科技观察

最近写了一个新的中间件“runtimedynamicloglevelswitch”,它使用JavaSPI机制实现自定义配置中心,保证良好的扩展性。在使用的过程中,突然发现SPI其实很像日常写API接口,然后实现。那么SPI和普通的API实现有什么区别呢?带着这个疑问,我们一起来梳理一下SPI机制。本文预计阅读10分钟,主要关注以下几点:什么是SPI机制?SPI实践案例SPI和API有什么区别?1、什么是SPI机制?SPI(ServiceProviderInterface)字面意思是服务提供者接口,本质上是一种“服务扩展机制”。为什么需要这样的“服务扩展机制”?因为系统中的抽象模块,比如日志模块、xml解析模块、jdbc模块等,往往会有很多不同的实现方案。为了满足可插拔性原则,我们一般推荐模块之间基于接口编程,模块之间不要硬编码实现类。这就需要“服务扩展机制”,然后就是SPI。SPI机制为我们的程序提供了扩展功能。不需要将框架的一些实现类写到代码中。我们在对应的配置文件中定义一个接口的实现类的全限定名,服务加载器读取配置文件并加载实现类。这样就可以在运行时为接口动态替换实现类。最常见的是Java的SPI机制。此外,还有Dubbo和SpringBoot自定义的SPI机制。二、SPI实践案例1、SPI在业界的实践案例简单了解了SPI的概念之后,我们来看看SPI在业界的实践案例,以及如何利用SPI实现灵活扩展。JDBC驱动程序已加载。SPI机制最常见的实践案例是JDBC驱动加载。利用Java的SPI机制,我们可以根据不同的数据库厂商,引入不同的JDBC驱动包。SpringBoot的SPI机制。用过SpringBoot的同学应该都知道,我们可以在spring.factories中加入我们自定义的自动配置类。这个特性在xxx-starter中用得特别广泛。Dubbo的SPI机制。基本上Dubbo的每个功能点都提供了扩展点,SPI机制被应用的淋漓尽致。例如,它提供了集群扩展、路由扩展、负载均衡扩展等近30个扩展点。如果一个Dubbo内置的实现不能满足我们的需求,那么我们只需要利用它的SPI机制,将我们的实现换成Dubbo的即可。2、如何在实际项目中使用以上三个例子,是业界最常见的SPI机制实现方式。下面我们就来看看我是如何在实际项目中使用JavaSPI机制实现自定义配置中心,保证良好的扩展性的。项目地址,路过可以点个星:)https://github.com/saigu/LogLevelSwitch。要求很简单。中间件“运行时动态日志级别切换”需要在应用运行时获取切换状态,然后动态改变应用的日志级别。如何获取开关状态?我们一般需要配置中心来处理。作为一个开源的中间件,使用它的应用可能有自己不同的配置中心(如Nacos、Apollo、springcloudconfig、自研配置中心等),因此必须支持接入自定义配置中心。这时候就需要SPI机制来实现了!(1)定义接口interfacepackageio.github.saigu.log.level.sw.listener;publicinterfaceConfigListener{/***获取初始开关状态*@return开关的初始上下文*/SwitchContextgetInitSwitch();/***获取变更配置*@paramchangedConfig变更配置上下文*/voidlistenChangedConfig(TchangedConfig);}(2)SPI加载本项目通过JavaSPI实现,无需依赖额外的组件,通过ServiceLoaderpublic动态加载classChangeListenerFactory{publicstaticConfigListenergetListener(){finalServiceLoaderloader=ServiceLoader.load(ConfigListener.class);for(ConfigListenerconfigListener:loader){returnconfigListener;}thrownewIllegal("请选择有效的监听器");}}(3)应用自定义配置中心访问使用该中间件的应用,只需三步即可访问自定义配置中心。STEP1:在应用中的pom中引入依赖。io.github.saigulog-switch-core1.0.0-beta第2步:构建配置Bean。@ConfigurationpublicclassLogLevelSwitchConfig{@BeanLogLevelSwitchlogLevelSwitch(){返回新的LogLevelSwitch();}}STEP3:访问??配置中心。声明配置中心的SPI实现。在资源路径下新建META-INF/services,新建文件io.github.saigu.log.level.sw.listener.ConfigListener,写入自定义配置中心的“实现类名”。3、SPI和API有什么区别?我们已经介绍了什么是SPI以及如何使用SPI机制。现在,回头看看开头提出的问题。SPI和API有什么区别?它们都需要定义接口interface,然后自定义实现类implements,看起来基本一样。有什么不同?各自的使用场景是什么?别着急,让我们从头开始解决。从“面向接口编程”的角度来看,“调用者”应该通过调用“接口”而不是“具体实现”来处理逻辑。那么,“接口”的定义到底应该在“调用方”还是“实现方”呢?理论上,有三种选择:“接口”定义在“实现端”。“接口”在“调用者”中定义。“接口”在单独的包中定义。(1)“接口”定义在“实现端”我们来看看“接口”定义在“实现端”的情况。这很容易理解。实现者同时提供“接口”和“实现类”。“调用者”可以参考接口来实现调用一个实现类的功能。这是我们每天使用的API。API最显着的特点是实现和接口在一个包中。自己定义接口,自己实现类。(2)“接口”是在“调用者”中定义的再来看看“接口”属于“调用者”的情况。这其实就是SPI机制。以JDBC驱动为例,“调用者”(用户或JDK)定义了java.sql.Driver接口,该接口位于“调用者”JDK的包中,各个数据库厂商都实现了该接口,如mysql驱动程序com。mysql.jdbc.驱动程序。因此,SPI最显着的特点是:“接口”在“调用者”包中,“调用者”定义规则,自定义实现类在“实现者”包中,然后加载实现类进入“呼叫者”中间。(3)“接口”定义在独立包中最后一种情况,如果一个“接口”在一个上下文中是API,在另一个上下文中是SPI,那么这个“接口”可以定义在一个独立的包中。4.小结本文介绍什么是SPI机制,然后结合行业案例和项目实践来说明SPI的使用场景,最后分析JavaSPI和API的区别。