Java SPI 原理、样例

在 Java 中,SPI(Service Provider Interface)全称为服务提供者接口,它是一种用于实现框架扩展和插件化的机制。

一、SPI 作用

允许在运行时动态地为接口查找服务实现,而不需要在代码中显式地指定具体的实现类。

这使得框架具有更好的可扩展性,第三方可以通过实现特定的接口来为框架提供额外的功能。这在很多框架和库中都有广泛的应用,例如数据库驱动、日志框架等。

二、基本原理

1、定义服务接口:首先定义一个接口或抽象类,这是服务的规范。
2、实现服务接口:编写接口的具体实现类。
3、注册服务实现:在 META-INF/services 目录下创建一个以接口全限定名为文件名的文件,并在该文件中列出所有实现类的全限定名。
4、加载服务实现:使用 ServiceLoader 类来加载和使用服务实现。

三、代码样例

1、定义服务接口

假设现在有一个权威机构,比如 Java,它需要对数据存储进行规范。

它定义了一个数据存储接口,和一个加载实现类的工具类。

package com.storage.specification;

/**
 * 数据存储接口
 */
public interface StorageProvider {

    void save(String data);
}
---------------------------------------------------
package com.storage.specification.spi;

import com.storage.specification.StorageProvider;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;

/**
 * 加载 存储服务 工具
 */
public class ProviderLoader {

    public static StorageProvider getStorageProvider(ClassLoader classLoader) {
        // 从 resources/META-INF/services 获取实现类名称,然后从类路径下加载 实现类
        ServiceLoader<StorageProvider> loader = ServiceLoader.load(StorageProvider.class, classLoader);
        Iterator<StorageProvider> iterator = loader.iterator();
        List<StorageProvider> providers = new ArrayList<>();
        while (iterator.hasNext()) {
            providers.add(iterator.next());
        }
        return providers.get(0);
    }
}

 步骤:

1)用IDEA 新建一个 maven 项目,取名叫 storage-specification

72210ef906724b369f01d2e4e93435c1.png

2)新建上述两个类

ef918c0b36394b809b2bf1b165f76832.png

3)install 项目到本地仓库,以便后面项目可以用 gav 坐标引用到

3f98b8a6f289464a86ba527dbecfacf3.png

2、实现服务接口

 现在有两个供应商 Mysql 和 Redis,它们拿到 Java 给的规范,分别提供他们的 二进制文件 存储实现和内存数据库实现。

Mysql 实现:

1)用IDEA 新建一个 maven 项目,取名叫 mysql-storage-provider

ce6ec51b40b84682bb9cf38d585bbeba.png

2)pom 文件依赖 存储规范 gav

d659cb7e4fe64bfa974a7c4a1aeb8ef5.png

3) 编写实现接口

package com.mysql.storage;

import com.storage.specification.StorageProvider;

public class MysqlStorageProvider implements StorageProvider {

    @Override
    public void save(String s) {
        System.out.println("mysql save " + s);
    }
}

4)在项目 resources/META-INF/services 目录下

创建一个以接口全限定名 

com.storage.specification.StorageProvider 为文件名的文件(注意不要带后缀),

并在该文件中列出所有实现类的全限定名 com.mysql.storage.MysqlStorageProvider

83c56e97667248b4802bbde0f36f0013.png

5)install 项目到本地仓库,以便后面项目可以用 gav 坐标引用到

422d9523acff44959d660b62988bc35f.png

 以同样的方式创建一个 Redis 项目,取名 redis-storage-provider

576d491552644239b74f4243976001cb.png

区别只在

1)实现类不同

package com.redis.storage;

import com.storage.specification.StorageProvider;

public class RedisStorageProvider implements StorageProvider {

    @Override
    public void save(String s) {
        System.out.println("redis save " + s);
    }
}

2) 实现类的全限定名不同

f9505b6029a4405ebff457d9ce1d567d.png

3)同样 install 项目到本地仓库,以便后面项目可以用 gav 坐标引用到

8a66d82e0ea14976b27b0ea2c2cfbdfa.png

3、定义服务使用者

1)用 IDEA 新建一个 maven 项目,取名叫 storage-user

b92732ff51ce4073879f42fdd7b8bada.png

2)pom 依赖引入 规范 gav 和 mysql gav

<dependencies>
        <dependency>
            <groupId>com.storage.specification</groupId>
            <artifactId>storage-specification</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.mysql.storage</groupId>
            <artifactId>mysql-storage-provider</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
</dependencies>

 3) 调用 mysql 存储

0e235b38189546b28c369ee0e7627049.png

4)切换 redis 依赖

<dependencies>
        <dependency>
            <groupId>com.storage.specification</groupId>
            <artifactId>storage-specification</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.redis.storage</groupId>
            <artifactId>redis-storage-provider</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
</dependencies>

 5)调用 redis 存储

可以看到,我们只修改了依赖的实现包,并没有修改代码,就实现了服务的替换。

而且我们的代码中并没有显示地调用服务的实现类。

355d9b822bfa4533b64df3954ee6ef98.png

四、总结

1、SPI 是 JAVA 提供给我们的一种用于实现框架扩展和插件化的机制。它可以让我们在不修改代码的时候,很轻松地替换服务的实现。

2、Spring 有自己的 SPI,Spring 的 SPI 路径是 resources/META-INF/spring.factories。

3、SPI 机制在框架集成中很常见。像 spring-web 使用的 javax.validation 就是通过 SPI 的方式集成了 HibernateValidator 实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值