首页 > 其他分享 >SPI原理

SPI原理

时间:2023-07-30 23:55:53浏览次数:36  
标签:配置文件 SPI 实现 接口 spi 服务提供者 原理

什么是SPI?

SPI全称为Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services目录中查找文件,自动加载文件中指定的实现类,并将它们实例化、初始化,然后返回给调用方使用。

其设计思想是:面向接口 + 配置化 + 反射。

SPI的优点

  1. 松耦合:SPI机制使得服务提供者和服务使用者之间松耦合,服务提供者可以独立地进行扩展和升级,而不会影响到服务使用者。

  2. 可扩展性:SPI机制可以很方便地扩展新的服务提供者实现类,只需要将实现类打包成jar包,并在META-INF/services目录下创建一个以服务接口全限定名为命名的文件,然后在文件中写入实现类的全限定名即可。

  3. 配置化:SPI机制可以通过配置文件来指定具体使用哪个服务提供者实现类,从而达到动态切换服务提供者的目的。

SPI的缺点

  1. 无法保证唯一性:SPI机制没有强制要求服务提供者实现类的唯一性,如果存在多个同名的服务提供者实现类,那么加载的时候就会出现问题。

  2. 无法进行参数传递:SPI机制只能用于无参数的构造函数创建实例,无法进行参数传递。

  3. 无法进行依赖注入:SPI机制只能通过反射来创建实例,无法进行依赖注入。

SPI实现具体步骤

  1. 定义接口:首先,需要定义一个接口,用于描述要实现的服务功能。

  2. 编写服务提供者:不同的模块可以实现这个接口,并提供自己的具体实现。

  3. 编写配置文件:在META-INF/services目录下,创建一个以接口的全限定名为名称的文本文件,其中包含所有实现了该接口的服务提供者的类名。这个配置文件的格式是每行一个服务提供者的类名。

  4. 加载服务提供者:在应用程序运行时,Java SPI机制会自动加载这个配置文件,并根据其中的类名实例化相应的服务提供者。

SPI的示例

项目关系图

 

spi-api项目代码

 

spi-plugin1项目代码

 资源文件中org.example.HelloService内容为:

org.plugin.a.SpiPluginA1
org.plugin.a.SpiPluginA2

spi-plugin2项目代码

 

 资源文件中org.example.HelloService内容为:

org.plugin.b.SpiPluginB1
org.plugin.b.SpiPluginB2

spi-app项目代码

spi-app项目不用关心Service的具体实现,它只需要和接口交互即可。

 在spi-app项目的pom中,引用spi-plugin1的jar包依赖

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>spi-plugin1</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

  运行spi-app项目,调用spi-plugin1中的实现,结果如下

 同样在spi-app项目的pom中,引用spi-plugin2的jar包依赖,其他不改动,则调用spi-plugin2中的实现,结果如下

 如果在spi-app项目的pom中,同时引用spi-plugin1、spi-plugin2的jar包依赖,则两个Service provider都会执行。

从以上示例可知:SPI定义好Interface之后,在项目中mave中只要引入对应不同实现的jar包,就可以调用不同的服务提供者实现类,从而达到动态切换的效果。

SPI的源码分析

Java SPI的实现主要依赖于ServiceLoader类。这个类是Java标准库中提供的,用于加载和实例化配置文件中指定的服务提供者。

ServiceLoader类的load方法接受一个接口类型作为参数,并返回一个ServiceLoader对象。通过这个对象,我们可以迭代获取接口的所有实现类的实例。

public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

 

ServiceLoader类的实现中,它会根据配置文件中的类名,使用反射机制来实例化服务提供者的对象。这样,我们就可以通过接口来引用具体的实现类,而无需在代码中显式地指定类名,实现了松耦合和可插拔的设计。

private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

 

SPI的应用场景

1. JDBC驱动程序加载:JDBC驱动程序加载就是一个典型的SPI应用场景。JDBC规范定义了一组接口,不同的数据库厂商需要实现这些接口,并将实现类打包成jar包,在META-INF/services目录下创建一个以接口全限定名为命名的文件,然后在文件中写入实现类的全限定名即可。当应用程序需要连接数据库时,就可以通过JDBC驱动程序管理器自动加载并初始化相应的驱动程序实现类。

2. 日志框架:许多日志框架(如Log4j、Logback等)都使用了SPI机制。日志框架定义了一组接口,并提供了默认的实现类。用户可以通过配置文件来指定使用哪个实现类。

3. RPC框架:RPC框架(如Dubbo、gRPC等)也使用了SPI机制。RPC框架定义了一组接口,不同的序列化、负载均衡、注册中心等组件需要实现这些接口,并将实现类打包成jar包,在META-INF/services目录下创建一个以接口全限定名为命名的文件,然后在文件中写入实现类的全限定名即可。当应用程序需要调用远程服务时,就可以通过RPC框架自动加载并初始化相应的组件实现类。

SPI和SpringBoot对比

 
SPI SpringBoot自动装配
使用配置文件:META-INF/serivce 使用文件META-INF/spring.factories
提供jar的一方,也一起提供配置文件 提供自动配置的jar包,也提供配置META-INF/spring.factories
使用getResource读取classpath中的配置文件 和spi读取配置文件的方法一样

标签:配置文件,SPI,实现,接口,spi,服务提供者,原理
From: https://www.cnblogs.com/techs/p/17524340.html

相关文章

  • AI夏令营-LightGBM的数学原理
    XGBoost提升集成学习模型的性能从基学习器本身入手从误差优化入手集成回归树的误差定义$argminL=\sum_{i=1}^nl(y_i,y_i^\Lambda)+\sum_{k=1}^K\Omega(f_k)$\(y_i\)是实际值,$y_i^\Lambda$是误差\(sl\Omega(f_k)=\gammaT+\frac{1}{2}\lambda\parallelw\p......
  • Log日志的实现原理
    log.info("XXX");打印日志的时候,Log组件会将“写入”动作封装成一个LogEvent事件,而这个事件的具体表现形式由LogFormat和MDC共同控制,Format决定了Log的输出格式,而MDC决定了输出什么内容。LogFormat Log组件定义了日志输出格式,这和我们平时使用“String.format”的方式差不多......
  • 无涯教程-jQuery - Spinner组件函数
    WidgetSpinner函数可与JqueryUI中的窗口小部件一起使用。Spinner提供了一种从一组中选择一个值的快速方法。Spinner-语法$("#menu").selectmenu();Spinner-示例以下是显示Spinner用法的简单示例-<!doctypehtml><htmllang="en"><head><metacharset="......
  • Arduino电机测速原理及代码实现
       //IncludetheTimerOneLibraryfromPaulStoffregen#include"TimerOne.h"//ConstantsforInterruptPins//ChangevaluesifnotusingArduinoUnoconstbyteMOTOR1=2;//Motor1InterruptPin-INT0constbyteMOTOR2=3;//Motor2......
  • 3_Spring_IOC原理分析_重要
    3_Spring_IOC原理分析_重要IOC底层原理1XML解析技术读取配置文件将上面的信息读取进入程序 对象的ID,一个是对象的类的全路径名2反射技术实例化对象,放到容器中获得类的字节码Classclazz=Class.forName("com.msb.dao.impl.EmpDaoImpl")通过字节码实例化对象Ob......
  • 11_Spring_AOP概念和原理
    11_Spring_AOP概念和原理AOP切面编程一般可以帮助我们在不修改现有代码的情况下,对程序的功能进行拓展,往往用于实现日志处理,权限控制,性能检测,事务控制等AOP实现的原理就是动态代理,在有接口的情况下,使用JDK动态代理,在没有接口的情况下使用cglib动态代理为Dao层所有的......
  • 深入理解TCP作为面向字节流协议的工作原理
    TCP(传输控制协议)是互联网中广泛使用的传输层协议,它负责可靠地传输数据流。一个重要的特性是TCP被称为面向字节流的协议。本文将详细介绍TCP作为面向字节流协议的含义,其工作原理以及与面向消息的协议的区别。1.什么是TCP面向字节流协议?TCP作为面向字节流的协议意味着数据在发送端......
  • Dubbo(一)_Java_SPI
    什么是SPI?Dubbo的源码中大量涉及了JavaSPI设计思想,所以理解SPI对理解Dubbo源码有很大帮助。JavaSPI全称JavaServiceProviderInterface,是Java提供的一种服务提供者发现机制。其核心功能是通过接口找到其实现类。在实际运用中,主要用在程序启动或者运行时,通过SPI机......
  • Dubbo(五)_服务注册原理
    服务注册是指将服务暴露出来的过程,包括了服务解析、服务启动、服务注册三部分。其中服务解析就是将Dubbo的服务配置解析成Spring的Bean对象;服务启动是启动一个可以处理请求的服务;服务注册是指将服务信息保存到注册中心中,供服务消费方获取。Dubbo的注册中心支持Redis、Zooke......
  • Dubbo(三)_spi
    DubboSPI源码分析DubboSPI的核心实现是ExtensionLoader,分析时先分析ExtensionLoader的成员变量和对公方法,依次分析扩展点的加载、扩展点的依赖注入、扩展点的自适应、扩展点的激活。分析中的名词约定:扩展点————扩展点实例化的对象,如org.apache.dubbo.rpc.protocol.d......