目录
- 实现:
- 示例:
- 注意事项:
Java SPI (Service Provider Interface) 是 Java 提供的一套用来发现和加载第三方服务的机制。SPI 的主要目的是为了解耦框架与插件(服务提供商)之间的依赖关系,使得框架能够灵活地在运行时根据配置加载不同的服务实现。
实现:
-
接口定义:定义一个接口或抽象类,声明一些方法作为服务规范。
-
服务提供者:实现上述接口或抽象类的具体类,这些实现类通常被打包在独立的 jar 文件中,并且在该 jar 文件的
META-INF/services
目录下创建一个以服务接口全限定名命名的文件。这个文件的内容就是实现类的全限定名,可以有多个实现类,每行一个。 -
服务发现:在应用程序中,通过
java.util.ServiceLoader
类来加载服务提供者的具体实现。ServiceLoader
会查找 classpath 下所有META-INF/services
目录下的配置文件,并根据文件中的类名实例化具体的实现类。
示例:
- 定义服务接口:创建一个接口,如
MyService
。
public interface MyService {void doSomething();
}
- 实现服务接口:创建该接口的一个或多个实现类,如
MyServiceImpl1
和MyServiceImpl2
。
public class MyServiceImpl1 implements MyService {@Overridepublic void doSomething() {System.out.println("MyServiceImpl1 is doing something.");}
}public class MyServiceImpl2 implements MyService {@Overridepublic void doSomething() {System.out.println("MyServiceImpl2 is doing something.");}
}
- 配置服务提供者:在实现类的 jar 包的
META-INF/services
目录下创建一个名为com.example.MyService
的文件(假设接口的全限定名为com.example.MyService
),文件内容分别写入实现类的全限定名。
com.example.MyServiceImpl1
com.example.MyServiceImpl2
- 使用 ServiceLoader 加载服务:在客户端代码中使用
ServiceLoader
加载并使用服务。
public class SpiClient {public static void main(String[] args) {ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);for (MyService service : loader) {service.doSomething();}}
}
运行 SpiClient
类,会依次打印出 MyServiceImpl1
和 MyServiceImpl2
的输出,表明这两个服务提供者都被成功加载并执行了。
注意事项:
- SPI 机制默认是懒加载,即首次迭代
ServiceLoader
的迭代器时才加载服务实现类。 - SPI 加载过程是线程安全的,但实例化服务提供者不是,如果实例化过程需要并发控制,需要自行处理。
- 由于 SPI 的实现是基于 classpath 扫描,因此它不适合动态加载服务实现,特别是在容器环境中。