JAVA SPI 是怎么实现的?
SPI 是什么?
SPI(Service Provider Interface) ,是 JDK 内置的一种提供发现机制。SPI 是一种动态替换发现的机制。
SPI类图
JAVA SPI 实现
- 定义一组接口,接口有多种实现
public interface IShout {
void shout();
}
public class Cat implements IShout {
@Override
public void shout() {
System.out.println("miao miao");
}
}
public class Dog implements IShout {
@Override
public void shout() {
System.out.println("wang wang");
}
}
- 在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 (org.foo.demo.IShout文件),内容是要应用的实现类(这里是org.foo.demo.animal.Dog和org.foo.demo.animal.Cat,每行一个类)。
文件位置
代码语言:javascript复制- src
-main
-resources
- META-INF
- services
- org.foo.demo.IShout
文件内容
代码语言:javascript复制org.foo.demo.animal.Dog
org.foo.demo.animal.Cat
- ServiceLoaader 加载配置文件中指定的实现
public class SPIMain {
public static void main(String[] args) {
ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class);
for (IShout s : shouts) {
s.shout();
}
}
}
输出:
代码语言:javascript复制wang wang
miao miao
Java SPI 实现原理
- 应用程序调用 ServiceLoader.load 方法创建一个 ServiceLoader,并实例化类中的成员变量。
- load(Classloader 类型,类加载器)
- acc(AccessControlContext 类型,访问控制器)
- providers(LinkedHashMap<String,S>)类型,用于缓存加载成功的类
- lookupIterators (实现迭代器功能)
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();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
- 应用程序通过迭代器接口 读取META-INF/services/下的配置文件,然后实例化:
1).读取配置文件
代码语言:javascript复制 if (configs == null) {
try {
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);
}
}
2). 通过反射方法Class.forName()加载类对象,并用instance()方法将类实例化。
3). 把实例化后的类缓存到providers对象中
JAVA SPI 应用场景
数据库DriverManager、Spring、ConfigurableBeanFactory等都用到了SPI机制。
在JDBC4.0之前,连接数据库的时候,通常会用Class.forName("com.mysql.jdbc.Driver")这句先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0之后不需要Class.forName来加载驱动,直接获取连接即可,这里使用了Java的SPI扩展机制来实现。
- 在mysql-connector-java-5.1.45.jar中,META-INF/services目录下会有一个名字为java.sql.Driver的文件:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
- Mysql DriverManager实现
DriverManager中有一个静态代码块如下:
代码语言:javascript复制static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
loadInitialDrivers方法,loadInitialDrivers用法用到了上文提到的spi工具类ServiceLoader:
代码语言:javascript复制 public void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
...
}
总结
Java的SPI机制就是为某个接口寻找到相关的服务实现