概述

SPI 全称 Service Provider Interface ,说白了,就是 Java 只提供接口,而不同厂商根据接口做不同的实现。Java 就可以根据 SPI 机制为某个接口寻找接口的实现类。这样就实现解耦和模块化。常见的 SPIJDBC 、日志门面接口、 SpringSpringBoot 相关starter组件、 DubboJNDI等等。

4. Java SPI 机制 - 图1

Demo

  1. 定义接口

    1. public interface MyJdbcService {
    2. void connect();
    3. }
  2. 服务提供者接口实现

    1. public class MyJdbcServiceImpl implements MyJdbcService{
    2. @Override
    3. public void connect() {
    4. System.out.println("My JDBC Connect.");
    5. }
    6. }
  3. **META-INF/services** 目录下创建文件

META-INF/services 目录下创建一个以 接口全限定名 为命名的文件,内容为实现类的全限定名。
spi-service-config.png

  1. 通过 ServiceLoader 获取实现类

    1. public class SpiDemo {
    2. public static void main(String[] args) {
    3. ServiceLoader<MyJdbcService> services = ServiceLoader.load(MyJdbcService.class);
    4. Iterator<MyJdbcService> iterator = services.iterator();
    5. while (iterator.hasNext()) {
    6. MyJdbcService myJdbcService = iterator.next();
    7. myJdbcService.connect();
    8. }
    9. }
    10. }
    11. // OUTPUT
    12. // My JDBC Connect.

    源码分析

    使用线程上下文来加载 Class

    1. public static <S> ServiceLoader<S> load(Class<S> service) {
    2. ClassLoader cl = Thread.currentThread().getContextClassLoader();
    3. return ServiceLoader.load(service, cl);
    4. }

    缺陷

  2. 不能按需加载,需要遍历所有实现,并实例化。如果不想用某些实现类,或者某些类实例化比较耗时,但它也被载入了。这就造成性能损失。

  3. 获取某个实现类的方式不够灵活,只能通过迭代器扣税。
  4. ServiceLoader 实例是非线程安全的。