浅谈JAVA中的SPI机制

一、SPI的机制定义

SPI,全称Service Provider Interface, 是Java中提供的一种服务发现机制,它允许应用程序动态地加载和使用第三方提供的服务实现,而无需在代码中显示引用这些实现类。通过将服务接口和其实现分离,从而具备更加好的可扩展性和可维护性。

Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制,将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。允许服务提供者通过实现调用者特定的接口来扩展系统,并且调用者能够动态地发现和加载这些服务提供者的实现类。

二、SPI的实现原理

2.1 Java SPI 实际上是"基于接口的编程+策略模式+配置文件"组合实现的动态加载机制。

2.2 实现SPI的步骤如下:

  1. 在服务提供者的JAR文件内,创建一个名为META-INF/services的目录。

  2. 在该目录下创建一个文件,文件名为服务接口的全限定名。

  3. 在该文件中列出服务接口的所有实现类。

2.3 ServiceLoader动态加载接口实现类的原理:类加载ClassLoader+迭代器模式

1. 初始化ServiceLoader类,传入自己想要加载的接口类
ServiceLoader<MyDriver> loader = ServiceLoader.load(MyDriver.class);

2. 内部还是用的当前线程上下文的ClassLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

3. 创建懒加载迭代器LazyIterator
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 void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

4. 通过遍历迭代器,寻找服务的实现类
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
}

private static final String PREFIX = "META-INF/services/";
5. 发现服务提供者的目录为fullName:"META-INF/services/"+服务类名
private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}


三、SPI在框架中的使用场景

调用者根据实际使用需要,动态启用或扩展的实现策略

  • 日志门面接口实现类加载SLF4J加载不同提供商的日志实现类

  • 数据库驱动加载接口实现类的加载JDBC加载不同类型数据库的驱动

  • SpringBoot的SPI机制:我们可以在 spring.factories 中加上我们自定义的自动配置类,事件监听器或初始化器等;

四、实际使用案例:

  1. 比如我现在有一个获取业务方数据的功能,获取数据的方式和想要的数据信息由我自己决定,但是想要的业务方数据很多,而且数据提供方不确定,也不知道有多少。此时可以采用JAVA中的SPI机制思想,由自己决定获取数据的接口,未来服务方只需要提供这个接口的实现类即可。

调用方接口制定:

public interface ThirdPartyQueryService {

    /**
     * 获取第三方服务信息
     *
     * @param projectId 项目id
     * @param infoId    信息id
     * @param userId    用户id
     * @return 信息
     */
    ThirdPatyData queryThirdPartyData(Integer projectId, Long infoId, Long userId);
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值