目录

  • 一、Java的SPI机制
  • 1、什么是Java的SPI ?
  • 2、JavaSPI 代码示例 (使用Maven项目演示)
  • 3、 JavaSPI 机制的核心-ServiceLoader
  • 4、实现自己的ServiceLoader
  • 5、Java中还有哪些SPI实现?


一、Java的SPI机制

1、什么是Java的SPI ?

SPI全称 Service Provider Interface ,字面意思:“服务提供者的接口”,是一种服务发现机制。
用于实现框架或库的扩展点,允许在运行时动态地插入或更换组件实现。它提供了一个框架来发现和加载服务实现,使得软件模块能够灵活地选择和使用不同的服务提供商。SPI鼓励松耦合的设计,因为服务的消费者不需要直接依赖于具体的服务实现。有没有想到面向对象的某个设计原则,SOLID中的 O 开闭原则,对扩展开放对修改关闭。Java允许服务提供者,按照SPI给定的规则实现自己的服务,而Java应用使用通过SPI规则提供的服务时无需进行额外的配置,并且可以随时替换服务,也无需修改业务代码。

SPI可以和我们常用的API对比着理解。

API全称、Application Programming Interface,字面意思:“应用程序编程接口” 。API是一组规则和定义,允许一个软件应用与另一个软件应用、库或操作系统进行交互。它定义了如何进行数据传输、请求服务或执行特定功能的协议和工具。API为开发人员提供了一种标准化的方式来访问和使用预先构建的功能,而无需了解这些功能内部的复杂实现细节。

总结:

名称

目的

使用者

举例

SPI

支持可插拔的架构,便于组件和服务的替换与扩展。

主要由服务提供者(如库、框架开发者)实现,但也需要应用开发者配置以启用特定的服务实现。(这里说的配置一般就是引入jar或者maven、gradle的坐标即可)

Java中,JDBC驱动的加载就是一个典型的SPI应用。

API

简化开发过程,提高效率,促进不同系统间的互操作性。

通常由应用开发者使用,以集成外部服务或内部模块。

语音识别 API、文件上传 API等

个人感觉SPI就是种"设计模式",只是不在常见的23种设计模式之中,Java利用"SPI这种设计模式" 实现灵活的选择服务供应商实现,来达到解耦的目的,以此提高程序的可修改性和灵活性。
SPI和外观设计模式也有相通之处、外观模式(Facade) 定义了一个高层接口,为子系统中的一组接口提供一个一致的界面,从而简化子系统的使用。

简单的图示对比:
拿NASA的重返月球计划举例。
NASA准备把重返月球计划的着陆器设计部分分包给商业航天公司来完成。

如果还按照API的方式来实现,那么可能就是下图中的结果。NASA想替换某个方案就必须修改系统去适配其他公司的API接口。
这样代码的耦合性就大大提高了。并且导致NASA失去了主动性。NASA这个甲方才不愿意这么干。 所以NASA决定采用SPI规则来约束供应商。

图文并茂带你理解Java的SPI机制_maven

NASA给出了关键性的技术指标。

假设目前有SpaceX 和 Blue Origin两家公司中标 去完成着陆器的设计。 这两家公司需要遵循NASA给定的SPI规则进行设计。

两家公司月球着陆器产品设计完成后,NASA只需要按照SPI规则去加载对应的产品即可。

图文并茂带你理解Java的SPI机制_apache_02

下图可以看出,虽然两家公司的火箭设计不一样,但是他们的接口都符合NASA指定的接口规则。这样NASA就可以很轻松的集成两家公司的方案,如果觉得SpaceX的方案不好,直接拿Blue Origin的方案无缝替代即可。

图文并茂带你理解Java的SPI机制_开发语言_03

2、JavaSPI 代码示例 (使用Maven项目演示)

①、NASA先定义SPI接口 并发布到本地仓库供SpaceX和BlueOrigin实现

项目结构:

图文并茂带你理解Java的SPI机制_maven_04


pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nasa</groupId>
    <artifactId>SPI-interface</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
</project>

maven打包安装到本地仓库

图文并茂带你理解Java的SPI机制_xml_05


打包好的jar名称

图文并茂带你理解Java的SPI机制_开发语言_06

SPI接口:

package com.nasa;
/**
 * NASA提供的SPI接口
 * */
public interface LandingOnTheMoon {
    /**
     * 着陆方法
     */
    void land();

}

SPI加载实现(稍后会详细介绍):

package com.nasa;

import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;

/**
 * 加载具体的服务实现
 * */
public class LandingOnTheMoonLoader {

    private static volatile LandingOnTheMoonLoader LOADER;

    private final LandingOnTheMoon landingOnTheMoon;

    private final List<LandingOnTheMoon> landingOnTheMoons;

    /**
     * 加载服务
     * */
    private LandingOnTheMoonLoader() {
        ServiceLoader<LandingOnTheMoon> loader = ServiceLoader.load(LandingOnTheMoon.class);
        List<LandingOnTheMoon> list = new ArrayList<>();
        for (LandingOnTheMoon landingOnTheMoon : loader) {
            list.add(landingOnTheMoon);
        }
        landingOnTheMoons = list;
        if (!list.isEmpty()) {
            // 取第一个
            landingOnTheMoon = list.get(0);
        } else {
            landingOnTheMoon = null;
        }
    }


    /**
     * LandingOnTheMoonLoader 单例加载
     * */
    public static LandingOnTheMoonLoader getLOADER() {
        if (LOADER == null) {
            synchronized (LandingOnTheMoonLoader.class) {
                if (LOADER == null) {
                    LOADER = new LandingOnTheMoonLoader();
                }
            }
        }
        return LOADER;
    }


    public void land(){
        if(landingOnTheMoons.isEmpty()){
            System.out.println("LandingOnTheMoon服务未加载!");
        }else {
            LandingOnTheMoon landingOnTheMoon = landingOnTheMoons.get(0);
            landingOnTheMoon.land();
        }
    }
}

②、SpaceX实现自己的登陆月球方案
先引入NASA 指定的SPI接口
pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.spacex</groupId>
    <artifactId>SpaceX</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>com.nasa</groupId>
            <artifactId>SPI-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

SpaceX的登陆月球实现:

package com.spacex;

import com.nasa.LandingOnTheMoon;

/**
 * SpaceX实现的月球着陆方案
 * */
public class SpaceXLand implements LandingOnTheMoon {
    @Override
    public void land() {
        System.out.println("SpaceX landing on the moon with StarShip~");
    }
}

SpaceX的登陆月球实现配置信息:

注意: Maven项目中 META-INF/services/SPI接口全限定名 需要放在resources目录下

图文并茂带你理解Java的SPI机制_java_07

内容为SpaceX实现类的全限定名 :

图文并茂带你理解Java的SPI机制_java_08


③、BlueOrigin实现自己的登陆月球方案

先引入NASA 指定的SPI接口
pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.blue</groupId>
    <artifactId>BlueOrigin</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>com.nasa</groupId>
            <artifactId>SPI-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

BlueOrigin的登陆月球实现:

package com.blue;

import com.nasa.LandingOnTheMoon;

/**
 * 蓝色起源实现的月球着陆方案
 * */
public class BlueOriginLand implements LandingOnTheMoon {
    @Override
    public void land() {
        System.out.println("BlueOrigin landing on the moon with  NewGlenn~");
    }
}

BlueOrigin的登陆月球实现配置信息:

Maven项目中 META-INF/services/SPI接口全限定名 需要放在resources目录下

图文并茂带你理解Java的SPI机制_开发语言_09


内容为BlueOrigin实现类的全限定名 :

图文并茂带你理解Java的SPI机制_maven_10

编写测试代码:
新建一个Maven项目

pom文件中引入SpaceX的实现坐标

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nasa</groupId>
    <artifactId>NASA-LANDING</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
    
        <!--使用蓝色起源的着陆实现-->
  <!--      <dependency>
            <groupId>com.blue</groupId>
            <artifactId>BlueOrigin</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>-->

        <!--使用SpaceX的着陆实现-->
        <dependency>
            <groupId>com.spacex</groupId>
            <artifactId>SpaceX</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>

</project>
package com.nasa;

public class LandingShow {

    public static void main(String[] args) {
        LandingOnTheMoonLoader service = LandingOnTheMoonLoader.getLOADER();
        service.land();
    }
}

执行结果:

SpaceX landing on the moon with StarShip~

此时如果NASA觉得SpaceX的方案不行,需要更换BlueOrigin的方案,操作起来非常简单,只需要注释掉SpaceX的坐标,添加BlueOrigin的坐标,业务代码完全不用改。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nasa</groupId>
    <artifactId>NASA-LANDING</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--使用蓝色起源的着陆实现-->
        <dependency>
            <groupId>com.blue</groupId>
            <artifactId>BlueOrigin</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!--使用SpaceX的着陆实现-->
<!--        <dependency>-->
<!--            <groupId>com.spacex</groupId>-->
<!--            <artifactId>SpaceX</artifactId>-->
<!--            <version>1.0-SNAPSHOT</version>-->
<!--        </dependency>-->

    </dependencies>

</project>

执行结果:

BlueOrigin landing on the moon with  NewGlenn~

3、 JavaSPI 机制的核心-ServiceLoader

上面代码中我们使用ServiceLoader 去加载具体的服务实现。

ServiceLoader 是从JDK1.6 开始提供的一个类,用于加载服务提供者。

图文并茂带你理解Java的SPI机制_开发语言_11

我们看下源码:
其中 String PREFIX = “META-INF/services/”;
这个就是JDK的SPI功能规定的具体服务实现的配置信息文件所在的目录 META-INF/services/

JDK的SPI规定 服务实现者需要在 META-INF/services/ 目录下 新建文件名为 SPI接口全限定类名的文件

文件内容为 服务实现者需要被加载的具体类的全限定类名

图文并茂带你理解Java的SPI机制_maven_12

我们上面代码在加载服务的时候调用了ServiceLoader.load(LandingOnTheMoon.class);方法

ServiceLoader<LandingOnTheMoon> loader = ServiceLoader.load(LandingOnTheMoon.class);

看下这个方法的源码:

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

可以看到这个方法 只是获取了当前线程的上下文类加载器 然后又调用了一个load的重载方法

public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

在重载的load方法中又调用了ServiceLoader的私有构造器

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();
    }

然后调用 reload();方法

public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

reload();方法里面 调用了 new LazyIterator(service, loader);

具体的类加载行为就是在LazyIterator内部类中完成的。
这里的 lookupIterator = new LazyIterator(service, loader); 创建LazyIterator 懒加载迭代器对象,并没有马上去加载SPI的具体服务。
当我们调用 for (LandingOnTheMoon landingOnTheMoon : loader) {…} 循环时,会触发迭代器,在ServiceLoader中,迭代器是由Iterable接口的iterator()方法提供的。当第一次调用iterator()时,如果之前没有创建过迭代器,它会创建一个新的LazyIterator实例。

对应下面的源码:

//  lookupIterator 在 调用reload 方法时 创建过了
private LazyIterator lookupIterator;
// 存储已经加载过的服务提供者实例  在 调用reload 方法时   调用了providers.clear(); 清空了这个集合
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
public Iterator<S> iterator() {
     return new Iterator<S>() {
     	 // 第一次调用时 providers 是空集合 会调用  lookupIterator 也就是 LazyIterator 的实例
         Iterator<Map.Entry<String,S>> knownProviders
             = providers.entrySet().iterator();

         public boolean hasNext() {
             if (knownProviders.hasNext())
                 return true;
             return lookupIterator.hasNext();
         }

         public S next() {
             if (knownProviders.hasNext())
                 return knownProviders.next().getValue();
             return lookupIterator.next();
         }

         public void remove() {
             throw new UnsupportedOperationException();
         }

     };
 }

下面看 LazyIterator 的实现 具体的类加载就在这里:
LazyIterator 的 hasNext 和 next方法 中判断了 acc 是否是null
其中 acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 这段代码是在 ServiceLoader初始化的时候执行的
主要用于判断 当前Java运行环境中是否存在安全管理器(SecurityManager),默认情况下不会配置,如果配置了SecurityManager,某些敏感操作(比如文件访问、网络连接等)可能会受到安全策略的限制或需要相应的权限检查。
这个不重要,我们还是看具体的方法 hasNextService 和 nextService方法。

public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

LazyIterator 的 hasNextService 和 nextService方法
终于到了加载具体服务的地方了。

// 迭代器
Iterator<String> pending = null;
  
private boolean hasNextService() {
      if (nextName != null) {
          return true;
      }
      if (configs == null) {
          try {
          	  // 这里获取 SPI配置文件的文件名  包含 META-INF/services/
              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;
  }


private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    // nextName 在hasNextService中已经被赋值了
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        //  关键点终于来了  最终还是利用Java的反射机制 完成类的加载
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
    }
    // 判断下 加载的 Class 的类型 是否实现了 给定的 SPI接口   拿上面NASA登月的例子看,这里的 service 就是  LandingOnTheMoon.class
    if (!service.isAssignableFrom(c)) {
        fail(service,
             "Provider " + cn  + " not a subtype");
    }
    try {
    	// 通过反射实例化 SPI的实现类 并且转换成 SPI接口类型
        S p = service.cast(c.newInstance());
        // 保存到  LinkedHashMap<String,S> providers 缓存中
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

看 具体的parse 方法(解析服务实现者提供的配置文件)

private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        // 保存解析配置文件内的 全限定类名
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            // 一行一行的读取配置信息 并将每行解析出的全限定类名 保存到 names 
            // parseLine 方法不用看了 就是解析字符串的
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        // 返回 拥有所有实现SPI接口的服务提供者全限定类名集合的迭代器
        return names.iterator();
    }

绕了半天 通过源码发现 最终类的加载还是通过 Java反射机制实现的。

那了解了ServiceLoader的具体实现之后,我们也可以实现一个自己的 服务加载器。
ServiceLoader只不过在实现了核心的SPI服务加载功能的基础上增加了一些额外的功能,比如通过LazyIterator 实现延迟加载,
可以调用reload() 在应用运行时实现服务的动态加载(不用重启应用就能加载服务),访问控制(比如判断 当前Java运行环境中是否存在安全管理器),还有按照加载顺序保存已加载的服务等等。不过ServiceLoader也是线程不安全的这点需要注意。

4、实现自己的ServiceLoader

我们实现一个最简单的ServiceLoader只考虑加载服务的功能,其他的安全、性能之类的都不考虑。
我们也仿照ServiceLoader类的结构,和SPI配置类规定的路径来实现。

package com.nasa;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;

public class MySimpleServiceLoader<T> {

    // 直接copy ServiceLoader的规则
    private static final String PREFIX = "META-INF/services/";


    // 定义的SPI接口
    private final Class<T> service;

    // 类加载器
    private final ClassLoader loader;

    // 保存已加载的服务实例
    private final LinkedHashMap<String, T> providers = new LinkedHashMap<>();

    // 构造方法T
    private MySimpleServiceLoader(Class<T> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        // 加载服务
        realLoad();
    }

    public static <T> MySimpleServiceLoader<T> load(Class<T> service) {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        return new MySimpleServiceLoader<>(service, contextClassLoader);
    }


    private void realLoad() {
        String fullName = PREFIX + service.getName();
        try {
            // 获取配置文件
            Enumeration<URL> resources = loader.getResources(fullName);
            // 读取配置文件
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                InputStream inputStream = url.openStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
                // 我们就不做解析错误之类的逻辑了  默认给的配置文件都是对的
                String name = reader.readLine();
                // 通过反射创建对象
                Class<?> aClass = Class.forName(name, false, loader);
                // 判断 服务实现类是否实现了 给定的SPI接口
                if (service.isAssignableFrom(aClass)) {
                    T cast = service.cast(aClass.newInstance());
                    providers.put(name, cast);
                }
            }
        } catch (Exception e) {
            System.out.println("出现异常!");
            e.printStackTrace();
        }
    }

    // 返回加载好的 服务实现
    public LinkedHashMap<String, T> getProviders() {
        return providers;
    }
}

使用自定义的服务加载器 MySimpleServiceLoader

package com.nasa;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * 加载具体的服务实现
 * */
public class LandingOnTheMoonLoader {

    private static volatile LandingOnTheMoonLoader LOADER;

    private final LandingOnTheMoon landingOnTheMoon;

    private final List<LandingOnTheMoon> landingOnTheMoons = new ArrayList<>();

    /**
     * 加载服务
     * */
    private LandingOnTheMoonLoader() {
        // 通过自定义的服务加载器 去加载服务实现
        MySimpleServiceLoader<LandingOnTheMoon> mySimpleServiceLoader = MySimpleServiceLoader.load(LandingOnTheMoon.class);
        LinkedHashMap<String, LandingOnTheMoon> providers = mySimpleServiceLoader.getProviders();

        providers.forEach((k,v)->{
            System.out.println(k);
            landingOnTheMoons.add(v);
        });
        if (!landingOnTheMoons.isEmpty()) {
            // 取第一个
            landingOnTheMoon = landingOnTheMoons.get(0);
        } else {
            landingOnTheMoon = null;
        }

    }


    /**
     * LandingOnTheMoonLoader 单例加载
     * */
    public static LandingOnTheMoonLoader getLOADER() {
        if (LOADER == null) {
            synchronized (LandingOnTheMoonLoader.class) {
                if (LOADER == null) {
                    LOADER = new LandingOnTheMoonLoader();
                }
            }
        }
        return LOADER;
    }


    public void land(){
        if(landingOnTheMoons.isEmpty()){
            System.out.println("LandingOnTheMoon服务未加载!");
        }else {
            LandingOnTheMoon landingOnTheMoon = landingOnTheMoons.get(0);
            landingOnTheMoon.land();
        }
    }

}

测试:

public static void main(String[] args) {
        LandingOnTheMoonLoader service = LandingOnTheMoonLoader.getLOADER();
        service.land();
    }

使用SpaceX的实现,结果如下:

com.spacex.SpaceXLand
SpaceX landing on the moon with StarShip~

5、Java中还有哪些SPI实现?

  • 1、JDBC (Java Database Connectivity): JDBC驱动的加载就是SPI的一个经典应用。各个数据库厂商提供自己的JDBC驱动实现,应用程序无需直接引用具体驱动的实现类,只需将驱动JAR包放入类路径,Java SPI机制会自动发现并加载合适的驱动。
  • 2、日志框架: 如SLF4J (Simple Logging Facade for Java) 和Logback、Log4j等,它们利用SPI机制来发现和加载具体的日志实现。用户可以根据需要选择或更换日志实现,而无需修改应用程序代码。
  • 3、Spring框架: 虽然Spring框架本身更倾向于使用其自身的bean工厂和依赖注入机制来管理组件和服务,但Spring也支持SPI机制,特别是在某些扩展点和与第三方库集成时。
  • 4、Dubbo: Apache Dubbo是一个高性能的RPC框架,它大量使用SPI机制来实现其插件体系,允许用户轻松替换或扩展序列化、负载均衡、集群容错等核心组件。
  • 5、JNDI (Java Naming and Directory Interface): JNDI服务提供者也是通过SPI机制注册和发现的,允许应用程序访问不同的命名和目录服务。
  • 6、JAX-WS (Java API for XML Web Services) 和 JAX-RS (Java API for RESTful Web Services): 这些Java Web服务技术框架使用SPI来发现和加载实现特定功能的服务提供者,比如SOAP绑定和HTTP连接器。
  • 7、JavaBeans Activation Framework (JAF): 用于处理MIME类型的邮件附件等,通过SPI来发现数据处理器。
  • 8、JavaMail: 用于发送和接收电子邮件的API,利用SPI来加载传输协议和其他服务提供者。