共计 2884 个字符,预计需要花费 8 分钟才能阅读完成。
大家好,我是不才陈某~
SPI(Service Provider Interface) 是 JDK 内置的一种服务提供发现机制,能够用来启用框架扩大和替换组件, 次要用于框架中开发,例如 Dubbo、Spring、Common-Logging,JDBC 等采纳采纳 SPI 机制,针对同一接口采纳不同的实现提供给不同的用户,从而进步了框架的扩展性。
之前也写过 Java SPI 的深刻分析:聊聊 Java SPI 机制
举荐 Java 工程师技术指南:https://github.com/chenjiabin…
Java SPI 实现
Java 内置的 SPI 通过 java.util.ServiceLoader 类解析 classPath 和 jar 包的 META-INF/services/ 目录 下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此实现调用。
示例阐明
创立动静接口
public interface VedioSPI
{void call();
}
实现类 1
public class Mp3Vedio implements VedioSPI
{
@Override
public void call()
{System.out.println("this is mp3 call");
}
}
实现类 2
public class Mp4Vedio implements VedioSPI
{
@Override
public void call()
{System.out.println("this is mp4 call");
}
}
在我的项目的 source 目录下新建 META-INF/services/ 目录下,创立 com.skywares.fw.juc.spi.VedioSPI 文件。
相干测试
public class VedioSPITest
{public static void main(String[] args)
{ServiceLoader<VedioSPI> serviceLoader =ServiceLoader.load(VedioSPI.class);
serviceLoader.forEach(t->{t.call();
});
}
}
阐明:Java 实现 spi 是通过 ServiceLoader 来查找服务提供的工具类。
运行后果:
源码剖析
上述只是通过简略的示例来实现下 java 的内置的 SPI 性能。其实现原理是 ServiceLoader 是 Java 内置的用于查找服务提供接口的工具类,通过调用 load() 办法实现对服务提供接口的查找,最初遍从来一一拜访服务提供接口的实现类。
从源码能够发现:
- ServiceLoader 类自身实现了 Iterable 接口并实现了其中的 iterator 办法,iterator 办法的实现中调用了 LazyIterator 这个外部类中的办法,迭代器创立实例。
- 所有服务提供接口的对应文件都是搁置在 META-INF/services/ 目录下,final 类型决定了 PREFIX 目录不可变更。
尽管 java 提供的 SPI 机制的思维十分好,然而也存在相应的弊病。具体如下:
- Java 内置的办法形式只能通过遍从来获取
- 服务提供接口必须放到 META-INF/services/ 目录下。
针对 java 的 spi 存在的问题,Spring 的 SPI 机制沿用的 SPI 的思维,但对其进行扩大和优化。
举荐 Java 工程师技术指南:https://github.com/chenjiabin…
Spring SPI
Spring SPI 沿用了 Java SPI 的设计思维,Spring 采纳的是 spring.factories 形式实现 SPI 机制,能够在不批改 Spring 源码的前提下,提供 Spring 框架的扩展性。
Spring 示例
定义接口
public interface DataBaseSPI
{void getConnection();
}
相干实现
##DB2 实现
public class DB2DataBase implements DataBaseSPI
{
@Override
public void getConnection()
{System.out.println("this database is db2");
}
}
##Mysql 实现
public class MysqlDataBase implements DataBaseSPI
{
@Override
public void getConnection()
{System.out.println("this is mysql database");
}
}
1、在我的项目的 META-INF 目录下,新增 spring.factories 文件
2、填写相干的接口信息,内容如下:
com.skywares.fw.juc.springspi.DataBaseSPI = com.skywares.fw.juc.springspi.DB2DataBase, com.skywares.fw.juc.springspi.MysqlDataBase
阐明多个实现采纳逗号分隔。
相干测试类
public class SpringSPITest
{public static void main(String[] args)
{
List<DataBaseSPI> dataBaseSPIs =SpringFactoriesLoader.loadFactories(DataBaseSPI.class,
Thread.currentThread().getContextClassLoader());
for(DataBaseSPI datBaseSPI:dataBaseSPIs){datBaseSPI.getConnection();
}
}
}
输入后果
从示例中咱们看出,Spring 采纳 spring.factories 实现 SPI 与 java 实现 SPI 十分类似,然而 spring 的 spi 形式针对 java 的 spi 进行的相干优化具体内容如下:
- Java SPI 是一个服务提供接口对应一个配置文件,配置文件中寄存以后接口的所有实现类,多个服务提供接口对应多个配置文件,所有配置都在 services 目录下;
- Spring factories SPI 是一个 spring.factories 配置文件寄存多个接口及对应的实现类,以接口全限定名作为 key,实现类作为 value 来配置,多个实现类用逗号隔开,仅 spring.factories 一个配置文件。
那么 spring 是如何通过加载 spring.factories 来实现 SpI 的呢? 咱们能够通过源码来进一步剖析。
举荐 Java 工程师技术指南:https://github.com/chenjiabin…
源码剖析
阐明:loadFactoryNames 解析 spring.factories 文件中指定接口的实现类的全限定名,具体实现如下:
阐明:获取所有 jar 包中 META-INF/spring.factories 文件门路,以枚举值返回。遍历 spring.factories 文件门路,一一加载解析,整合 factoryClass 类型的实现类名称,获取到实现类的全类名称后进行类的实例话操作,其相干源码如下:
阐明:实例化是通过反射来实现对应的初始化。