SPI : service provider interface, 其实际是java提供的一套用于被第三方实现或扩展的API
其体现了 java 基于接口编程的思想, 可以搭配策略模式以及配置文件实现自定义扩展
例如 :数据库驱动,日志,spring,以及dubbo/druid等
关于SPI 相关配置文件
根据 java.util.ServiceLoader 中的注释, 首先需要创建一个 待扩展类全名称的文件, 对于 待扩展类建议要求使用 interface / abstract class ,并不推荐使用 concrete class;
对于文件中的内容要求为当前服务提供者的子级(继承/实现),且要求必须存在一个公共的无参构造函数支持实例化
对于文件所处路径必须为 META-INF/services 路径中
具体使用
使用 java.util.ServiceLoader#load(java.lang.Class), 其还存在一个重载方法支持传递自定义classLoader
通过传递serviceProvider 类型来创建一个ServiceLoader对象, 通过获取其迭代器,遍历迭代器来实现扩展服务加载;
对于服务实现类的加载是在调用 迭代时生成的, 第一次加载时主要是利用到了反射, 当第一次加载完成后会存储到本地缓存中
代码示例
自定义接口作为 serviceProvider
/** Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved*/
packagecom.xingguo.spi;/*** Animal
*
*@authorguoxing
* @date 2020/12/20 12:17 PM
*@since
*/
public interfaceAnimal {voideat();
}
自定义相关实现类
/** Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved*/
packagecom.xingguo.spi;/*** Cat
*
*@authorguoxing
* @date 2020/12/20 12:18 PM
*@since
*/
public class Cat implementsAnimal {
@Overridepublic voideat() {
System.out.println("cat eat fish!!!");
}
}
/** Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved*/
packagecom.xingguo.spi;/*** Human
*
*@authorguoxing
* @date 2020/12/20 12:19 PM
*@since
*/
public class Human implementsAnimal {
@Overridepublic voideat() {
System.out.println("human eat anything what they can eat");
}
}
自定义spi文件(无后缀名)
文件路径 META-INF/services/com.xingguo.spi.Animal
文件中的内容, 使用"#"表示注释
#当前文件中也可以不存在任何内容
#要求当前concrete class 必须和当前文件名称所对应的类 存在层级关系,否则运行时会抛出错误
#且要求实现类中必须存在一个公共的无参构造函数
com.xingguo.spi.Cat
com.xingguo.spi.Human
测试类
/** Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved*/
packagecom.xingguo.spi;importjava.util.Iterator;importjava.util.ServiceLoader;/*** AnimalSPIDemo
* {@linkServiceLoader}
* {@linkAnimal src/META-INF/services/com.xingguo.spi.Animal}
*
*@authorguoxing
* @date 2020/12/20 12:20 PM
*@since
*/
public classAnimalSPIDemo {public static voidmain(String[] args) {
ServiceLoader animals = ServiceLoader.load(Animal.class, Thread.currentThread().getContextClassLoader());
Iterator iterator =animals.iterator();while(iterator.hasNext()) {
Animal next=iterator.next();
next.eat();
}
}
}
利用SPI实现 数据库 驱动加载
通过spi的方式来替代直接使用 Class.forName 加载的方式
要求工程引入相关数据库厂商的驱动jar包
spi文件定义
文件路径 META-INF/services/java.sql.Driver
文件中的内容
# mysql driver
com.mysql.cj.jdbc.Driver
相关测试类
/** Copyright (c) 2020, guoxing, Co,. Ltd. All Rights Reserved*/
packagecom.xingguo.spi.driver;importjava.sql.Driver;importjava.util.Iterator;importjava.util.ServiceLoader;/*** SqlDriverDemo
*
*@authorguoxing
* @date 2020/12/20 1:04 PM
*@since
*/
public classSqlDriverDemo {public static voidmain(String[] args) {
ServiceLoader load = ServiceLoader.load(Driver.class);
Iterator iterator =load.iterator();while(iterator.hasNext()) {
System.out.println(iterator.next()); // 输出的结果为 com.mysql.cj.jdbc.Driver@对象内存地址
}
}
}