三种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注解。
相关推荐
Bunny021215 分钟前
SpringMVC笔记
java·redis·笔记
feng_blog668843 分钟前
【docker-1】快速入门docker
java·docker·eureka
枫叶落雨2222 小时前
04JavaWeb——Maven-SpringBootWeb入门
java·maven
m0_748232392 小时前
SpringMVC新版本踩坑[已解决]
java
码农小灰2 小时前
Spring MVC中HandlerInterceptor和Filter的区别
java·spring·mvc
乔木剑衣3 小时前
Java集合学习:HashMap的原理
java·学习·哈希算法·集合
专职4 小时前
spring boot中实现手动分页
java·spring boot·后端
神探阿航4 小时前
第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
java·算法·蓝桥杯
梓沂4 小时前
idea修改模块名导致程序编译出错
java·ide·intellij-idea
m0_748230445 小时前
创建一个Spring Boot项目
java·spring boot·后端