Java服务提供之SPI

58 阅读3分钟

前言

  Java SPI是一种用于实现解耦和扩展的机制,它使得开发者能够在不修改代码的情况下,动态地替换、添加或移除实现。通过Java SPI,我们可以将核心代码与具体实现解耦,实现高度可扩展的架构。本文将深入探索Java SPI的工作原理、使用方法和一些最佳实践

API和SPI区别

API是一组定义了软件组件之间交互方式的接口集合,通常由提供者自行开发。
SPI是一种软件设计模式,用于实现可插拔的组件扩展。 image.png

使用场景

  1. 扩展框架功能:SPI使得框架能够以插件的形式扩展功能。开发者可以实现特定的接口并提供相应的服务提供者,框架会在运行时动态地加载和调用这些服务提供者的实现。这样,框架的功能可以被灵活地扩展和定制,而不需要修改框架的核心代码。
  2. 应用程序配置:SPI允许应用程序在不修改代码的情况下,通过替换或添加服务提供者来改变应用程序的行为。这对于应用程序的配置和定制非常有用。例如,应用程序可能依赖于某种数据库,通过SPI,可以在配置中指定不同的数据库服务提供者,从而无需更改应用程序代码即可切换数据库。
  3. 插件化系统:SPI可用于构建插件化系统,使得开发者能够以插件的形式扩展系统的功能。系统定义一组接口,并提供SPI机制,插件开发者可以实现这些接口并提供相应的插件。系统在运行时会加载和管理这些插件,从而实现系统功能的动态扩展和定制。
  4. 模块化开发:SPI在模块化开发中也有广泛应用。模块可以定义一组服务接口,并通过SPI机制来获取实现这些接口的服务提供者。这样,模块之间可以实现解耦,每个模块只关注自己的功能实现,而不需要显式依赖其他模块的具体实现。

使用示例

首先定义一个接口Pay, 这个接口有发起充值的功能,但是我现在并不确定有哪些第三方支付,变提供了第三方接口。

public interface Pay {

    void pay();
}
public class WechatPay implements Pay {
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}
public class Alipay implements Pay{
    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}

当实现好后,需要在src/main/resources/ 下建立 /META-INF/services 目录文件夹下新增一个文件,文件名为该接口的全限定名即:com.cementwork.test.demo.spi.Pay, 内容为接口实现的全限定名。

com.cementwork.test.demo.spi.WechatPay
com.cementwork.test.demo.spi.Alipay

编写测试类

public static void main(String[] args) {
    ServiceLoader<Pay> load = ServiceLoader.load(Pay.class);
    Iterator<Pay> iterator = load.iterator();
    while (iterator.hasNext()){
        Pay next = iterator.next();
        next.pay();
    }
}

image.png

实现原理

核心在ServiceLoader这个类中

//扫描的前缀路径
private static final String PREFIX = "META-INF/services/";

// 被扫描的文件名
private final Class<S> service;

//类加载器
private final ClassLoader loader;

// 上下文对象
private final AccessControlContext acc;

//按照实例化的顺序缓存已经实例化的类
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

//懒加载迭代器
private LazyIterator lookupIterator;
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);
        }
    }
    //循环扫描configs中所有的包路径
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        pending = parse(service, configs.nextElement());
    }
    //pending缓存了所有查找到的类全限定名
    nextName = pending.next();
    return true;
}

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
}

总结