概述
SPI的全称是Service Provider Interface, 直译过来就是"服务提供接口",为了降低耦合,实现在模块装配的时候动态指定具体实现类的一种服务发现机制。
概述
参考博客 https://www.cnblogs.com/oskyhg/p/10800051.html
要使用Java SPI,需要遵循如下约定:
-
1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
-
2、接口实现类所在的jar包放在主程序的classpath中;
-
3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
-
4、SPI的实现类必须携带一个不带参数的构造方法;
java SPI应用场景很广泛,在Java底层和一些框架中都很常用,比如java数据驱动加载和Dubbo。Java底层定义加载接口后,由不同的厂商提供驱动加载的实现方式,当我们需要加载不同的数据库的时候,只需要替换数据库对应的驱动加载jar包,就可以进行使用。
SPI实现Demo
git地址:https://gitee.com/pengzhang_master/jdk-spi
项目结构
SPI-api模块提供接口
package com;
public interface SpiDemo {
void say();
}
SPI-Impl1提供实现类
package com;
public class SpiDemoImpl1 implements SpiDemo {
public void say() {
System.out.println("SpiDemoImpl1");
}
}
在resource下创建MATE-INF.services,创建文件com.SpiDemo,可以看到这个文件名对应SPI-api的接口路径,在该文件下填写本模块的实现类路径com.SpiDemoImpl1
pom文件依赖SPI-api模块
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>SPI-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
SPI-Impl2模块和SPI-Impl1模块类似,只是将SpiDemoImpl1改为SpiDemoImpl2
SPI-main模块依赖
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>SPI-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>SPI-Impl1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>SPI-Impl2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
测试
package com;
import java.util.ServiceLoader;
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<SpiDemo> serviceLoader = ServiceLoader.load(SpiDemo.class);
for (SpiDemo o : serviceLoader) {
o.say();
}
}
}
SPI源码
从ServiceLoader.load(SpiDemo.class);一路进去会看到有个懒加载迭代器lookupIterator = new LazyIterator(service, loader);
LazyIterator这个迭代器只需要关心hasNext()和next(), hasNext()里面又只是单纯地调用hasNextService(). next()里面单纯地调用了nextService();
private boolean hasNextService() {
if (nextName != null) {
// nextName不为空,说明加载过了,而且服务不为空
return true;
}
// configs就是所有的实现类文件名字
if (configs == null) {
try {
// PREFIX是 /META-INF/services
// service.getName() 是接口的全限定名称
String fullName = PREFIX + service.getName();
// loader == null, 说明是bootstrap类加载器
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;
}
//就是判断一下configs.nextElement()的格式是不是对的
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
// 校验一下
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 尝试一下是否能加载该类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,"Provider " + cn + " not found");
}
// 是不是service的子类,或者同一个类
if (!service.isAssignableFrom(c)) {
fail(service,"Provider " + cn + " not a subtype");
}
try {
// 实例化这个类, 然后向上转一下
S p = service.cast(c.newInstance());
// 缓存起来,避免重复加载
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,"Provider " + cn + " could not be instantiated",x);
}
throw new Error(); // This cannot happen
}
所谓的懒加载,就是等到调用hasNext()再查找服务, 调用next()才实例化服务类.
服务提供商安装约定,将具体的实现类名称放到/META-INF/services/xxx下, ServiceLoader就可以根据服务提供者的意愿, 加载不同的实现了, 避免硬编码写死逻辑, 从而达到解耦的目的.
最后
以上就是朴实蜜蜂为你收集整理的JDK的SPI机制概述SPI实现DemoSPI源码的全部内容,希望文章能够帮你解决JDK的SPI机制概述SPI实现DemoSPI源码所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复