Java SPI(Service Provider Interface)
目的
面向对象设计鼓励模块间基于接口而非具体实现编程,以降低模块间的耦合,遵循依赖倒置原则,并支持开闭原则(对扩展开放,对修改封闭)。然而,直接依赖具体实现会导致在替换实现时需要修改代码,违背了开闭原则。
Service Provider Interface(SPI)将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。

图下方SPI实例中接口与调用方紧密结合,而实现方可以是不同的提供方。
具体的实例:数据库加载驱动、日志接口等
使用
接口
定义功能接口,其中包含接口中提供的功能方法。
1 | package top.chc.api; |
调用方使用
调用方引入上述接口,并通过ServiceLoader.load()寻找路径下所有实现该接口的实现类。
1 | package top.chc.call; |
以上便是调用方的全部代码了,后续不需要再修改上述所有代码,即调用方代码无需再修改。
实现方代码
实现方引入上述接口,并创建该接口的实现类。
1 | package com.chc; |
调用方配置
配置调用方工程的 resources/META-INF/services 文件夹,创建文件src/main/resources/META-INF/services/top.chc.api.MyService并写入实现类的权限定名。
- 路径:
resources/META-INF/services/ - 文件名:接口的全限定名
- 文件内容:实现类的全限定名(一个或多个,每行一个)
1 | # 文件路径:src/main/resources/META-INF/services/top.chc.api.MyService |
工作原理:
ServiceLoader 工作原理:
- 找到
META-INF/services/<接口全名>文件 - 读取里面的每一行实现类名
- 用
Class.forName()加载这些类 - 通过无参构造方法
newInstance()实例化
其中 接口类 与 实现类 都必须放在调用方的 classpath 中。
代码实现
创建三个工程:
- api项目:提供接口
- achieve项目:提供实现类,其中包含api项目
- use项目:调用类,其中包含api项目和achieve项目