剖析 SPI 在 Spring 中的应用
一、概述
SPI(Service Provider Interface),是 Java 内置的一种服务提供发现机制,可以用来提高框架的扩展性,主要用于框架的开发中,比如 Dubbo,不同框架中实现略有差异,但核心机制相同,而 Java 的 SPI 机制可以为接口寻找服务实现。SPI 机制将服务的具体实现转移到了程序外,为框架的扩展和解耦提供了极大的便利。
得益于 SPI 优秀的能力,为模块功能的动态扩展提供了很好的支撑。
本文会先简单介绍 Java 内置的 SPI 和 Dubbo 中的 SPI 应用,重点介绍分析 Spring 中的 SPI 机制,对比 Spring SPI 和 Java 内置的 SPI 以及与 Dubbo SPI 的异同。
二、Java SPI
Java 内置的 SPI 通过 java.util.ServiceLoader 类解析 classPath 和 jar 包的 META-INF/services/ 目录 下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此完成调用。
1. Java SPI
先通过代码来了解下 Java SPI 的实现
① 创建服务提供接口:
package jdk.spi;
// 接口
public interface DataBaseSPI {
public void dataBaseOperation();
}
② 创建服务提供接口的实现类
MysqlDataBaseSPIImpl
实现类 1:
package jdk.spi.impl;
import jdk.spi.DataBaseSPI;
public class MysqlDataBaseSPIImpl implements DataBaseSPI {
@Override
public void dataBaseOperation() {
System.out.println("Operate Mysql database!!!");
}
}OracleDataBaseSPIImpl
实现类 2:
package jdk.spi.impl;
import jdk.spi.DataBaseSPI;
public class OracleDataBaseSPIImpl implements DataBaseSPI {
@Override
public void dataBaseOperation() {
System.out.println("Operate Oracle database!!!");
}
}
③ 在项目 META-INF/services/ 目录下创建 jdk.spi.DataBaseSPI 文件
jdk.spi.DataBaseSPI:
jdk.spi.impl.MysqlDataBaseSPIImpl
jdk.spi.impl.OracleDataBaseSPIImpl
④ 运行代码:
JdkSpiTest#main()
package jdk.spi;
import java.util.ServiceLoader;
public class JdkSpiTest {
public static void main(String args[]){
// 加载jdk.spi.DataBaseSPI文件中DataBaseSPI的实现类(懒加载)
ServiceLoaderdataBaseSpis = ServiceLoader.load(DataBaseSPI.class);
// ServiceLoader实现了Iterable,故此处可以使用for循环遍历加载到的实现类
for(DataBaseSPI spi : dataBaseSpis){
spi.dataBaseOperation();
}
}
}
⑤ 运行结果:
Operate Mysql database!!!
Operate Oracle database!!!2. 源码分析
上述实现即为使用 Java 内置 SPI 实现的简单示例,ServiceLoader 是 Java 内置的用于查找服务提供接口的工具类,通过调用 load () 方法实现对服务提供接口的查找 (严格意义上此步并未真正的开始查找,只做初始化),最后遍历来逐个访问服务提供接口的实现类。
上述访问服务实现类的方式很不方便,如:无法直接使用某个服务,需要通过遍历来访问服务提供接口的各个实现,到此很多同学会有疑问:
Java 内置的访问方式只能通过遍历实现吗?服务提供接口必须放到 META-INF/services/ 目录下?是否可以放到其他目录下?
在分析源码之前先给出答案:两个都是的;Java 内置的 SPI 机制只能通过遍历的方式访问服务提供接口的实现类,而且服务提供接口的配置文件也只能放在 META-INF/services/ 目录下。
ServiceLoader 部分源码:
public final class ServiceLoader implements Iterable{
// 服务提供接口对应文件放置目录
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class service;
// 类加载器
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// 按照初始化顺序缓存服务提供接口实例
private LinkedHashMap providers = new LinkedHashMap