1.JAVA 的SPI机制
SPI全称(service provider interface)
,是JDK内置的一种服务提供发现机制,目前市面上有很多框架都是用它来做服务的扩展发现,大家耳熟能详的如JDBC、日志框架都有用到;
简单来说,它是一种动态替换发现的机制。举个简单的例子,如果我们定义了一个规范,需要第三方厂商去实现,那么对于我们应用方来说,只需要集成对应厂商的插件,既可以完成对应规范的实现机制。 形成一种插拔式的扩展手段
。
2.实现一个SPI机制
- 下面是自己来模拟spi来加载数据库驱动的流程图
数据库的驱动DataBaseDriver
/**
* @Project: 3.DistributedProject
* @description: 模拟数据库的驱动
* @author: sunkang
* @create: 2018-06-30 19:33
* @ModificationHistory who when What
**/
public interface DataBaseDriver {
String connect(String url);
}
MysqlDriver的驱动
/**
* @Project: java-spi
* @description: 模拟MysqlDriver的驱动
* @author: sunkang
* @create: 2018-06-30 19:34
* @ModificationHistory who when What
**/
public class MysqlDriver implements DataBaseDriver {
@Override
public String connect(String url) {
return "mySql driver : "+url;
}
}
- 在资源目录下的META-INF/services/目录下新建com.java.spi.DataBaseDriver的文件
com.java.spi.DataBaseDriver表示为接口的名称,com.java.spi.MysqlDriver为驱动的具体实现类
com.java.spi.MysqlDriver
spi应用的启动的例子
/**
* @Project: java-spi
* @description: 应用的启动的例子
* @author: sunkang
* @create: 2018-06-30 19:33
* @ModificationHistory who when What
**/
public class JavaSpiDemo
{
public static void main( String[] args )
{
ServiceLoader<DataBaseDriver> serviceLoader =ServiceLoader.load(DataBaseDriver.class);
for(DataBaseDriver dataBaseDriver :serviceLoader){
System.out.println(dataBaseDriver.connect("com.sunkang "));
}
}
}
- 输出结果为:
mySql driver : com.sunkang
3.SPI规范总结
实现SPI,就需要按照SPI本身定义的规范来进行配置,SPI规范如下
1.需要在classpath下创建一个目录,该目录命名必须是:META-INF/services
2.在该目录下创建一个文件,该文件需要满足以下几个条件
文件名必须是扩展的接口的全路径名称
文件内部描述的是该扩展接口的所有实现类
文件的编码格式是UTF-8
3.通过java.util.ServiceLoader的加载机制来发现
4.SPI的实际应用
SPI在很多地方有应用,大家可以看看最常用的java.sql.Driver驱动。JDK官方提供了java.sql.Driver这个驱动扩展点,但是你们并没有看到JDK中有对应的Driver实现。 那在哪里实现呢?
以连接Mysql为例,我们需要添加mysql-connector-java依赖。然后,你们可以在这个jar包中找到SPI的配置信息。如下图,所以java.sql.Driver由各个数据库厂商自行实现。这就是SPI的实际应用。当然除了这个意外,大家在spring的包中也可以看到相应的痕迹
5.SPI的缺点
1.JDK标准的SPI会一次性加载实例化扩展点的所有实现
,什么意思呢?就是如果你在META-INF/service下的文件里面加了N个实现类,那么JDK启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到,那么会很浪费资源
2.如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因
6.ServiceLoader类的分析
ServiceLoader<DataBaseDriver> serviceLoader =ServiceLoader.load(DataBaseDriver.class);
for(DataBaseDriver dataBaseDriver :serviceLoader){
System.out.println(dataBaseDriver.connect("com.sunkang "));
}
public final class ServiceLoader<S> implements Iterable<S>
该ServiceLoader实现了Iterable的接口,具体了迭代器的功也就可以进行for循环了
该load的方法会会初始化LazyIterator的迭代器
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
那么的对应引入的驱动是如何发现并实例化的,可以猜想:
先通过扫描下 META-INF/services+接口名,如果资源存在,则读取具体的内容进行反射实例化
对应的源码这里就不解析了,的确也是这样做的。