三种SPI机制的了解及使用

文章目录

      • 1.SPI机制概念
      • [2.Java SPI](#2.Java SPI)
        • [2.1 创建一个项目,并创建如下模块](#2.1 创建一个项目,并创建如下模块)
        • [2.2 db-api模块](#2.2 db-api模块)
        • [2.3 mysql-impl模块](#2.3 mysql-impl模块)
        • [2.4 oracle-impl模块](#2.4 oracle-impl模块)
        • [2.5 main-project模块](#2.5 main-project模块)
      • [3.Spring SPI](#3.Spring SPI)
      • [4.Dubbo SPI](#4.Dubbo SPI)

1.SPI机制概念

SPI全程Service Provider Interface,是一种服务发现机制。

SPI的本质就是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类型。这样就可以运行时,动态为接口替换实现类。

正因此特性,我们可以很容易的通过SPI机制为我们的程序提供拓展功能。

2.Java SPI

2.1 创建一个项目,并创建如下模块

注意模块之间需要对应引用依赖,比如:db-api需要被其他三个模块引入,main-project要引入两个实现模块依赖。

2.2 db-api模块
java 复制代码
public interface DBApi {
    void getDBImpl();
}
2.3 mysql-impl模块

实现DBApi接口,并且在resources下创建META-INF/services/全限定接口名称文件,这里如上图所示,然后文件中写实现类的全限定类名。

text 复制代码
com.linging.impl.MysqlImpl
2.4 oracle-impl模块

同上,文件中写oracle的实现类全限定类名:

text 复制代码
com.linging.impl.OracleImpl
2.5 main-project模块

引入依赖:

pom 复制代码
 <dependencies>
        <dependency>
            <groupId>com.linging</groupId>
            <artifactId>db-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.linging</groupId>
            <artifactId>mysql-impl</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.linging</groupId>
            <artifactId>oracle-impl</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
java 复制代码
public class Application {

    public static void main(String[] args) {
        ServiceLoader<DBApi> serviceLoader = ServiceLoader.load(DBApi.class);
        Iterator<DBApi> it = serviceLoader.iterator();
        while(it.hasNext()){
            DBApi next = it.next();
            // 判断具体实现类,进行处理...
            if(next.getClass().equals(MysqlImpl.class)){
                System.out.println("找到特定实现类");
            }
            next.getDBImpl();
        }
    }
}

通过上面的解析,可以发现,使用JavaSPI机制存在一些缺陷:

  • 不能按需加载,需要遍历所有实现,并实例化,然后在循环中才能找到我们需要的实现类,当某个实现类实例化很耗时,它也被载入并实例化,这就造成加载耗时过长。
  • 多个并发多线程使用ServiceLoader类的实例不安全。

3.Spring SPI

Spring SPI沿用了Java SPI的设计思想,采用spring.factories方式实现SPI机制,可以在不修改源码的前提下,提供Spring框架的扩展性。

引入spring依赖:

pom 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

db-api模块:

java 复制代码
public interface DBApi {
    void getDBImpl();
}

xxx-impl实现模块的不同:

spring,factories中的内容:

text 复制代码
com.linging.api.DBApi=com.linging.impl.MysqlImpl

main-project模块:

java 复制代码
public class Application {

    public static void main(String[] args) {
        List<DBApi> loadFactories =
                SpringFactoriesLoader.loadFactories(DBApi.class, Thread.currentThread().getContextClassLoader());
        Iterator<DBApi> it = loadFactories.iterator();
        while(it.hasNext()){
            DBApi next = it.next();
            next.getDBImpl();
        }
    }
}

Spring SPI机制和JavaSPI机制很类似,但是还有一些差异:

  • JavaSPI机制是一个服务提供接口对应一个配置文件,配置文件中存放当前接口的所有实现类,所有配置文件放在META-INF/services目录下。
  • SpringSPI是仅有一个META-INF/spring.factories配置文件存放多个接口及对应的实现类,以接口限定类名作为key,实现类作为value,多个实现类用逗号隔开。
  • 和JavaSPI一样,SpringSPI也无法获取某个固定的实现,只能按顺序遍历获取所有实现,从中获取自己想要的实现。

4.Dubbo SPI


引入dubbo依赖:

pom 复制代码
<dependency>
     <groupId>org.apache.dubbo</groupId>
     <artifactId>dubbo</artifactId>
     <version>2.7.12</version>
 </dependency>

db-api模块:

java 复制代码
@SPI
public interface DBApi {
    void getDBImpl();
}

xxx-impl模块:

配置文件内容:

xml 复制代码
mysql=com.linging.impl.MysqlImpl

main-project模块:

java 复制代码
public class Application {
    public static void main(String[] args) {
        ExtensionLoader<DBApi> extensionLoader = ExtensionLoader.getExtensionLoader(DBApi.class);
        // 按需加载
        DBApi mysql = extensionLoader.getExtension("mysql");
        mysql.getDBImpl();
    }
}
  • 基于JavaSPI的缺陷无法支持接口按需加载接口实现类,Dubbo并未使用JavaSPI,而是重新实现了一套功能更强的SPI机制。
  • Dubbo SPI的相关逻辑被封装在ExtensionLoader类中,通过它可以加载指定的实现类。
  • Dubbo SPI所需的配置文件放在META-INF/dubbo路径下,配置内容如下:文件名为接口全限定类名,内容为key=value的键值对,key为实现类的标识别名,value为实现类的全限定类名,这样就可以实现按需加载了。
  • 在使用时,需要在接口上加@SPI注解。
相关推荐
李慕婉学姐4 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆5 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin6 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20056 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉6 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国6 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882486 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈7 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_997 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹7 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理