SPI机制在JDK/Spring/SpringBoot/Dubbo的区别?
SPI机制在JDK/Spring/SpringBoot/Dubbo的区别?
概要
SPI (Service Provider Interface) 是一种服务发现机制,它允许第三方提供者为核心库或主框架提供实现或扩展。这种设计允许核心库/框架在不修改自身代码的情况下,通过第三方实现来增强功能。
一、JDK原生的SPI
Java SPI 是 Java 标准的一部分,任何符合 Java SPI 规范的框架或库都可以使用它。Java SPI 是一个纯粹的 Java API,没有其他框架的支持,因此需要手动管理和配置。
1. 应用示例
在Java的生态系统中,SPI 是一个核心概念,允许开发者提供扩展和替代的实现,而核心库或应用不必更改,下面举出一个例子来说明。
1)首先定义一个接口
public interface HelloSpi { String getName(); void handle(); }
2)定义不同的实现类
public class OneHelloSpiImpl implements HelloSpi { @Override public String getName() { return "One"; } @Override public void handle() { System.out.println(getName() + "执行"); } } public class TwoHelloSpiImpl implements HelloSpi { @Override public String getName() { return "Two"; } @Override public void handle() { System.out.println(getName() + "执行"); } }
3)在指定目录(META-INF/services)下创建文件
文件名是接口的全类名,文件内容是实现类的全类名。
这里创建的文件名是 org.example.chapter15.HelloSpi , 里面的内容为:
org.example.chapter15.OneHelloSpiImpl
org.example.chapter15.TwoHelloSpiImpl
JDK原生的SPI主要通过在META-INF/services/目录下放置特定的文件来指定哪些类实现了给定的服务接口(可能会有多个),每个文件对应一个接口或服务类型。这些文件的名称应为接口的全限定名,内容为实现该接口的全限定类名。
4)测试
public class Test { public static void main(String[] args) { ServiceLoader<HelloSpi> load = ServiceLoader.load(HelloSpi.class); Iterator<HelloSpi> iterator = load.iterator(); while (iterator.hasNext()) { HelloSpi next = iterator.next(); System.out.println(next.getName() + " 准备执行"); next.handle(); } System.out.println("执行结束"); } } //执行结果 //One 准备执行 //One执行 //Two 准备执行 //Two执行 //执行结束
通过执行结果我们可以看出,HelloSpi接口的所有实现类都得到了调用,我们可以通过这种机制根据不同的业务场景实现拓展的效果。示例是通过ServiceLoader实现的,我们来看一下这个类。
4. ServiceLoader
ServiceLoader是一个简单的服务提供者加载工具。是JDK6引进的一个特性。
部分源码如下:
public final class ServiceLoader<S> implements Iterable<S> { //指定服务提供者配置文件的基本路径 private static final String PREFIX = "META-INF/services/"; private final ClassLoader loader; //构造器 private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); //默认是系统类加载器(也叫做应用程序类加载器) loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } public static <S> ServiceLoader<S> load(Class<S> service) { //获取当前线程的线程上下文类加载器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); //调用下面的方法 return ServiceLoader.load(service, cl); } //根据指定的服务接口(service)和类加载器(cl)加载服务提供者 public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) { return new ServiceLoader<>(service, loader); } //... }
ServiceLoader类使用Java的类加载器机制从META-INF/services/目录下加载和实例化服务提供者。例如,ServiceLoader.load(MyServiceInterface.class)会返回一个实现了MyServiceInterface的实例迭代器。
load方法是通过获取当前线程的线程上下文类加载器实例来加载的。Java应用运行的初始线程的上下文类加载器默认是系统类加载器。这里其实破坏了双亲委派模型,因为Java应用收到类加载的请求时,按照双亲委派模型会向上请求父类加载器完成,这里并没有这么做 。
5. 缺点
JDK原生的SPI每次通过ServiceLoader加载时都会初始化一个新的实例,没有实现类的缓存,也没有考虑单例等高级功能。无法做到按需加载或者按需获取某个具体的实现。
6. 应用场景
Java中有许多我们常见的框架使用SPI机制的地方,JDBC,Dubbo,Logback等,Spring中也有使用。
二、Spring的SPI
Spring SPI 是基于 Java SPI 的机制,但 Spring 为了支持更多的功能和扩展,进行了封装和增强。Spring SPI 用于扩展 Spring 的核心功能,例如扩展 BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor 或自定义事件监听器等。与JDK的SPI不同,Spring的SPI与其IoC (Inversion of Control) 容器集成,通过Spring容器管理第三方库集成。
Spring的SPI体现在下面几个特点:
- 依然使用 META-INF/services 文件来声明服务,但 Spring 可以利用容器管理这些服务。
- 通过 Spring 容器和 Bean 的生命周期管理,自动完成服务加载和管理。
- 可以结合 Spring 的其他特性(如依赖注入、自动配置)进行更加灵活的扩展。
Spring SPI 更侧重于框架内部扩展点的管理(例如 Spring 的 ApplicationListener 或自定义 BeanPostProcessor)。
三、SpringBoot的SPI
1. 自动配置
Spring Boot SPI 是在 Spring Framework SPI 的基础上增强的机制,主要用于支持 Spring Boot 应用中的自动配置和模块化扩展。Spring Boot SPI 通过 META-INF/spring.factories 文件来注册自动配置类,极大简化了 Spring Boot 应用的配置过程。这个文件可以在多个jar中存在,并且Spring Boot会加载所有可见的spring.factories文件。
Spring Boot SPI 是在 Spring Boot 启动时自动加载并配置相关 Bean,它依赖于 Spring Boot 的自动配置机制,通常会结合 @Configuration 类和 @Conditional 注解来判断是否需要加载某个配置。
2. 应用示例
1)创建自动配置类
@Configuration @ConditionalOnClass(HikariDataSource.class) public class DataSourceAutoConfiguration { @Bean public DataSource dataSource() { // 假设自动配置一个数据库连接池 return new HikariDataSource(); } }
Spring Boot 使用 @Conditional 注解(如 @ConditionalOnClass, @ConditionalOnProperty 等)来控制自动配置是否生效。例如,如果检测到应用类路径中有 HikariCP,则会自动配置数据源。
2)spring.factories 配置文件
在 META-INF/spring.factories 文件中列出所有自动配置类。Spring Boot 启动时会加载这些配置类并应用到 Spring 容器中。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
3. 自动配置加载
当 Spring Boot 启动时,会扫描 所有jar包中的spring.factories 文件,加载并执行配置类中的自动配置逻辑。这使得开发者无需手动配置数据源,Spring Boot 会根据条件自动配置数据源。
4. 使用场景
1)为第三方库或自定义组件提供自动配置。
2)扩展 Spring Boot 启动过程,减少用户手动配置。
3)支持 Spring Boot 项目的快速集成和插件化开发。
四、Dubbo SPI
Dubbo 中实现了一套新的 SPI 机制,功能更强大,也更复杂一些。相关逻辑被封装在了ExtensionLoader类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容举例如下:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外在使用时还需要在接口上标注 @SPI注解,例如:
@SPI public interface Robot { void sayHello(); } public class OptimusPrime implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Optimus Prime."); } } public class Bumblebee implements Robot { @Override public void sayHello() { System.out.println("Hello, I am Bumblebee."); } } public class DubboSPITest { @Test public void sayHello() throws Exception { ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class); Robot optimusPrime = extensionLoader.getExtension("optimusPrime"); optimusPrime.sayHello(); Robot bumblebee = extensionLoader.getExtension("bumblebee"); bumblebee.sayHello(); } }
Dubbo SPI和JDK SPI最大的区别就在于支持“别名”,可以通过某个扩展点的别名来获取固定的扩展点。就像上面的例子中,我可以获取Robot多个SPI实现中别名为“optimusPrime”的实现,也可以获取别名为“bumblebee”的实现,这个功能非常有用!
通过@SPI注解的value属性,还可以默认一个“别名”的实现。比如在Dubbo中,默认的是Dubbo私有协议:dubbo protocol - dubbo://
来看看Dubbo中协议的接口:
@SPI("dubbo") public interface Protocol { ...... }
在Protocol接口上,增加了一个@SPI注解,而注解的value值为Dubbo ,通过SPI获取实现时就会获取 Protocol SPI配置中别名为dubbo的那个实现,com.alibaba.dubbo.rpc.Protocol文件如下:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper mock=com.alibaba.dubbo.rpc.support.MockProtocol dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
然后只需要通过getDefaultExtension,就可以获取到@SPI注解上value对应的那个扩展实现了。
五、总结
1. Java SPI 是最基本的服务提供者接口机制,适用于插件化架构和服务扩展。需要手动管理和配置。
2. Spring SPI 在 Java SPI 基础上,利用 Spring 框架的容器和管理能力,提供了更加灵活的扩展机制,适用于 Spring 框架本身的扩展。
3. Spring Boot SPI 是 Spring SPI 的进一步增强,特别强调自动配置,可以大大简化 Spring Boot 项目中第三方库的集成和配置过程。
4. Dubbo的SPI 支持别名,可以通过名称获取扩展点的某个固定实现,配合Dubbo SPI的注解很方便。另外,还支持Dubbo内部的依赖注入,通过目录来区分Dubbo 内置SPI和外部SPI,优先加载内部,保证内部的优先级最高。
参考链接:
https://juejin.cn/post/7197070078361387069
https://blog.csdn.net/qq_43592352/article/details/131002868
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)