SPI机制是什么?
名词解释
spi全称为 (Service Provider Interface),是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制,一种解耦非常优秀的思想。
工作原理
在ClassPath路径下的META-INF/services文件夹中, 以接口的全限定名来命名文件名,文件里面写该接口的实现。然后再资源加载的方式,读取文件的内容(接口实现的全限定名), 然后再去加载类。
优点
- 使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
缺点
虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。 多个并发多线程使用ServiceLoader类的实例是不安全的。
SPI机制示例
比如现在有这样一个场景:不同券商流水格式不同,解析逻辑也不同,比如项目里有券商A的流水解析模块,A券商就使用A解析模块,项目里有券商B的流水解析模块,B券商就使用B解析模块,先看下如何使用
- 创建META-INF/services文件夹,然后创建一个以Parse接口全限定名为名字的文件
- 在文件中编写想要实现哪个Parser的实现类(ParserA,ParserB),注意也要是全限定名。假如是是ParserA的模块,上面文件的内容:com.wudi.spitest.spi.strategy.ParserA
- 获取Parser并调用,获取并调用的逻辑,我就修改下上面的策略模式中的Context的invokerStrategy方法,这里假设默认使用第一个
- 顺便说一下Throwable可以输出异常信息打印,Exception不能输出异常信息打印
解析接口和实现类
public interface Parser {
/**
* 券商流水解析
* @param historyJson 券商流水原始流水
* @return 解析流水实体类
*/
List<StockHistory> parseByJson(String historyJson);
}
public class ParserA implements Parser{
@Override
public List<StockHistory> parseByJson(String historyJson) {
System.out.println("Parser A parseByJson");
return Collections.emptyList();
}
}
public class ParserB implements Parser{
@Override
public List<StockHistory> parseByJson(String historyJson) {
System.out.println("Parser B parseByJson");
return Collections.emptyList();
}
}
资源文件,文件内容为指定的parser实现
测试功能
public class Loader {
public static void main(String[] args) {
Loader loader = new Loader();
loader.invokeStrategy("json");
}
public void invokeStrategy(String json) {
try {
ServiceLoader<Parser> payServiceLoader = ServiceLoader.load(Parser.class);
Iterator<Parser> iterator = payServiceLoader.iterator();
if (iterator.hasNext()) {
iterator.next().parseByJson(json);
//控制台输出 Parser B parseByJson
}
}catch (Exception e){
System.out.println("找不到实现类");
e.printStackTrace();
}
}
}
一些实现案例
当我们的项目里面使用引用了mysql的驱动pom依赖时,我们的项目里面会自动选择使用mysql的驱动,我们甚至不需要手动去加载。我们来看看它的具体实现 源码类方法注释
Connection connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl","xxx","xxxx");
DriverManager loadInitialDrivers
一些更厉害的实现案例
springboot ...
- 讲讲springboot自动装配核心入口,顺带说下@import的作用,@import 注解的作用 zhuanlan.zhihu.com/p/147025312
- import AutoConfigurationImportSelector类后,他的作用是干嘛的?selectImports
- 说说方法 getCandidateConfigurations 区分下springboot的版本的不同引入文件的不同
- 在拿到META-INF/spring.factories中bean全名后,什么时机创建bean的 还有创建bean的条件
- 拓展调用链 refresh > invokeBeanFactoryPostProcessors > postProcessBeanDefinitionRegistry > ConfigurationClassPostProcessor > processConfigBeanDefinitions (核心处理扫描注解) parser.parse(candidates); his.deferredImportSelectorHandler.process();
总结
几乎不会用到java spi机制,springboot的自动装配yyds