SPI 学习

1. 什么是 SPI,和 API 有什么区别?

SPI (Service Provider Interface),和 API 只是概念上(语义上)的区别。

调用方来制定接口,实现方来针对接口来实现不同的实现,调用方来选择自己需要的实现方,这种插件化的设计叫SPI

个人理解,调别人的 interface 叫“调 API”;而自己定个interface,等别人实现、自己去 callback “回调别人的实现”,叫 SPI。
这种造概念很无聊,所以你会看到 “xxx 既是 API 又是 SPI”之类的话。
https://stackoverflow.com/questions/2954372/difference-between-spi-and-api
https://blog.csdn.net/jyxmust/article/details/82562242

image.png

2. SPI 设计:如何依赖注入,如何“自动绑定服务提供者”

2.1. 抽象来看,SPI 框架由哪些组成

< Effective Java, 2nd Edition>
https://stackoverflow.com/questions/2954372/difference-between-spi-and-api

There are three essential components of a service provider framework:

  • a service interface, which providers implement;
  • a provider registration API, which the system uses to register implementations, giving clients access to them;
  • and a service access API, which clients use to obtain an instance of the service
  • An optional fourth component of a service provider framework is a service provider interface
    , which providers implement to create instances of their service implementation. In the absence of a service provider interface, implementations are registered by class name and instantiated reflectively (Item 53)

例子:

In the case of JDBC, Connection plays the part of the service interface, DriverManager.registerDriver is the provider registration API, DriverManager.getConnection is the service access API, and Driver is the service provider interface.

2.2. 可供选择的实现

A. 定个 bean interface,通过 spring 依赖注入

B. 静态工厂方法,通过静态方法注入

比如 https://stackoverflow.com/a/2956981/7861127

// Noninstantiable class for service registration and access
public class Services {
    private Services() { }  // Prevents instantiation (Item 4)

    // Maps service names to services
    private static final Map providers =
        new ConcurrentHashMap();
    public static final String DEFAULT_PROVIDER_NAME = "";

    // Provider registration API
    public static void registerDefaultProvider(Provider p) {
        registerProvider(DEFAULT_PROVIDER_NAME, p);
    }
    public static void registerProvider(String name, Provider p){
        providers.put(name, p);
    }

    // Service access API
    public static Service newInstance() {
        return newInstance(DEFAULT_PROVIDER_NAME);
    }
    public static Service newInstance(String name) {
        Provider p = providers.get(name);
        if (p == null)
            throw new IllegalArgumentException(
                "No provider registered with name: " + name);
        return p.newService();
    }
}

B2. 懒注入,由用户传参指定“注入哪个 class”

自动静态注入有点隐蔽,且使用者不好灵活选择用哪个。因此可以改成“由使用者(开发者)显式传参、指定用哪个”。
比如 DriverManager spi:

image.png

https://zhuanlan.zhihu.com/p/28909673

注:其实 mysql 的 driver 内部用的还是 SPI 框架。driver 框架自己封装了一个让用户传字符串构造的工厂函数,方便没依赖 SPI 的driver实现(比如oracle的)

C. jdk 有一套 SPI 框架

原理和上面类似,只不过需要把“绑定哪个实现类”的配置写在每个jar包的 META-INF/services/接口全名 文件里,框架根据文件内容去“实例化”对应实现类。
这里所谓的实例化,指的是:

通过反射方法Class.forName()加载类对象,并用newInstance方法将类实例化,并把实例化后的类缓存到providers对象中,(LinkedHashMap类型)

本质上可以理解成“由每个插件包通过配置来(告诉框架)把自己注入进去”,从而在用户看来“引入jar包就自动注入”
https://pdai.tech/md/java/advanced/java-advanced-spi.html
https://zhuanlan.zhihu.com/p/28909673

注:在 slf4j 1.8 之后:采用 Java SPI (Service Provider Interface) 机制

D. 靠“同名 class 冒名顶替”来依赖注入

即 slf4j 1.8 版本之前的 trick,编译完后把 class 删了,期待实现包里有同名 class "冒名顶替",实现依赖注入


image.png

https://www.xin3721.com/Articlejava/29054.html

E. 使用OSGi作为插件系统的基础

比如 Eclipse
https://zhuanlan.zhihu.com/p/28909673
OSGI 因为“过度设计”、“过于复杂”而名声不好,我就不深入学习了

3. 其他容易混淆的概念

API (Application Programming Interface) vs ABI (Application Binary Interface)

ABI是约束编译器构建出程序的二进制规范
https://stackoverflow.com/questions/3784389/difference-between-api-and-abi
https://www.jianshu.com/p/bd77c842f281
https://blog.csdn.net/K346K346/article/details/88371267
https://blog.csdn.net/xinghun_4/article/details/7905298

你可能感兴趣的:(SPI 学习)