SPI(Service Provider Interface)服务提供接口是Java提供的一种用于服务发现和扩展的标准机制,主要用于实现模块化开发和插件化扩展。

SPI 机制允许服务提供者通过特定的配置文件将自己的实现注册到系统中,
然后系统通过反射机制动态加载这些实现,而不需要修改原始框架的代码,从而实现了系统的解耦、提高了可扩展性。

这样的设计极大地增强了软件系统的可扩展性和灵活性。

应用场景

一个典型的 SPI 应用场景是 JDBC(Java 数据库连接库),不同的数据库驱动程序开发者可以使用 JDBC 库,然后定制自己的数据库驱动程序。

框架扩展

许多Java框架利用SPI机制来支持可插拔的组件和服务,例如JDBC驱动加载、Dubbo的扩展点加载、Spring Boot的自动配置等。

我们使用的主流 Java 开发框架中,几乎都使用到了 SPI 机制,比如 Servlet 容器、日志框架、ORM 框架、Spring 框架。

所以这是 Java 开发者必须掌握的一个重要特性!


SPI 实现

系统配置文件实现

Java 内已经提供了 SPI 机制相关的 API 接口,可以直接使用,这种方式最简单。

  1. 首先创建一个 META-INF/services 目录,然后在其中创建一个配置文件,文件名就是要实现的接口的空文件。
  2. 在配置文件中写入自己定制的接口实现类的完整类路径。
    com.sihai.rpc.serializer.JdkSerializer
    
  3. 直接使用系统内置的 ServiceLoader 动态加载指定接口的实现类,代码如下:
    1
    2
    3
    4
    5
    6
    // 指定序列化器
    Serializer serializer = null;
    ServiceLoader<Serializer> serviceLoader = ServiceLoader.load(Serializer.class);
    for (Serializer service : serviceLoader) {
    serializer = service;
    }
    上述代码能够获取到所有文件中编写的实现类对象,选择一个使用即可。

自定义配置文件实现

系统实现 SPI 虽然简单,但是如果我们想定制多个不同的接口实现类,就没办法在框架中指定使用哪一个了,也就无法实现我们 “通过配置快速指定序列化器” 的需求。

所以我们需要自己定义 SPI 机制的实现,只要能够根据配置加载到类即可。

比如读取如下配置文件,能够得到一个 序列化器名称 => 序列化器实现类对象 的映射,就可以根据用户配置的序列化器名称动态加载指定实现类对象。

1
2
3
4
jdk=com.sihai.rpc.serializer.JdkSerializer
hessian=com.sihai.rpc.serializer.HessianSerializer
json=com.sihai.rpc.serializer.JsonSerializer
kryo=com.sihai.rpc.serializer.KryoSerializer

自定义序列化器

使用自定义的 SPI 机制实现,支持用户自定义序列化器并指定键名。

指定 SPI 配置目录。

系统内置的 SPI 机制默认是读取 META-INF/services 目录下的配置文件,我们可以自定义序列化器的路径,改为读取 META-INF/rpc 目录。

还可以将 SPI 配置再分为系统内置 SPI 和 用户自定义 SPI,这样用户可以自定义自己的 SPI 配置,系统内置 SPI 配置可以由系统管理员统一配置。

  • 用户自定义 SPI :META-INF/rpc/custom 目录,用户可以在该目录下新建配置,加载自定义的实现类。
  • 系统内置 SPI :META-INF/rpc/system 目录,RPC 框架自带的视线类,例如之前开发的 JdkSerializer。

这样,所有接口的实现类都可以通过 SPI 机制动态加载,不需要在代码中硬编码 Map 来维护实现类。

之后,我们如果要实现自定义的序列化器,只需要进行以下步骤:

  • 写一个类实现 Serializer 接口
  • 在 custom 目录下编写 SPI 配置文件,加载自己写的实现类