spi机制是什么

165 阅读3分钟

SPI机制是什么?

名词解释

spi全称为 (Service Provider Interface),是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制,一种解耦非常优秀的思想。

工作原理

在ClassPath路径下的META-INF/services文件夹中, 以接口的全限定名来命名文件名,文件里面写该接口的实现。然后再资源加载的方式,读取文件的内容(接口实现的全限定名), 然后再去加载类。

优点

  • 使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点

虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。 多个并发多线程使用ServiceLoader类的实例是不安全的。

image.png

SPI机制示例

比如现在有这样一个场景:不同券商流水格式不同,解析逻辑也不同,比如项目里有券商A的流水解析模块,A券商就使用A解析模块,项目里有券商B的流水解析模块,B券商就使用B解析模块,先看下如何使用

  1. 创建META-INF/services文件夹,然后创建一个以Parse接口全限定名为名字的文件
  2. 在文件中编写想要实现哪个Parser的实现类(ParserA,ParserB),注意也要是全限定名。假如是是ParserA的模块,上面文件的内容:com.wudi.spitest.spi.strategy.ParserA
  3. 获取Parser并调用,获取并调用的逻辑,我就修改下上面的策略模式中的Context的invokerStrategy方法,这里假设默认使用第一个
  4. 顺便说一下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实现

image.png 测试功能

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 ...

  1. 讲讲springboot自动装配核心入口,顺带说下@import的作用,@import 注解的作用 zhuanlan.zhihu.com/p/147025312
  2. import AutoConfigurationImportSelector类后,他的作用是干嘛的?selectImports
  3. 说说方法 getCandidateConfigurations 区分下springboot的版本的不同引入文件的不同
  4. 在拿到META-INF/spring.factories中bean全名后,什么时机创建bean的 还有创建bean的条件
  5. 拓展调用链 refresh > invokeBeanFactoryPostProcessors > postProcessBeanDefinitionRegistry > ConfigurationClassPostProcessor > processConfigBeanDefinitions (核心处理扫描注解) parser.parse(candidates); his.deferredImportSelectorHandler.process();

总结

几乎不会用到java spi机制,springboot的自动装配yyds