一文读懂SPI机制

一文读懂SPI机制

1、问题

什么是SPI?

2、答案

  • 要给出名词解释:SPI全称为:Service Provider Intreface,直驿为服务提供者接口,它是 JDK里面内置的一种动态扩展的一个实现(这里顺便提一句,也是打破JVM规范中的双亲委派机制的实现之一,因为它是由上层加载器加载下层具体实现的一个不优雅的实现方案)
  • 原理:简单来说,我们可以定义一个标准的接口,然后按照一定规则提供给第三方使用,第三方库里面可以去实现这个接口,那么我们在程序运行的时候,通过自己定义的规则(多指配置信息),去加载第三方库对这个接口的实现,从而去完成功能的一个动态扩展

有原理一定有案例实现才算完整

SPI机制非常典型的一个应用案例就是数据库的一个驱动: java.jdbc.Driver

它是一个接口,而jdk并没有提供这样一个实现,具体的实现是由数据库的厂商来完成的。

优点:SPI这种方式可以很好的解决不同框架之间的扩展问题**

3、那么,底层如何完成呢?

第三方在 /META-INF/services下,以要实现的SPI接口为文件名,具体的实现类为文件内容,创建一个配置文件。

JavaServiceLoader 会去加载配置文件,并加载对应的具体实现类。

如果同一个SPI接口有多个实现,可以通过ServiceLoader.iterator去遍历获取所有具体实现类。

在一个项目中,引入 mysql-connector-java.jar实现,然后在自定义一个实现了 java.sql.Driver SPI接口的 com.mybatis.demo.spi.DriverImpl实现类,并在 resources下,定义好 /META-INF/services/java.sql.Driver文件,其内容为 DriverImpl的全限定名,这样我们就有了Driver实现。

public static void main(String[] args) {
  ServiceLoader load = ServiceLoader.load(Driver.class);
  load.iterator().forEachRemaining(action -> {
    System.out.println(action.getClass());
  });
}

输出结果如下:

class com.mybatis.demo.spi.DriverImpl
class com.mysql.cj.jdbc.Driver

4、SPI实现类的类加载器是什么?

ServiceLoader通过线程上下文获取加载实现类的classloader,一般情况下是 application classloader,当然也可以自定义class loader。

public static <s> ServiceLoader<s> load(Class<s> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
}

public static <s> ServiceLoader<s> load(Class<s> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }
</s></s></s></s></s></s>

Connect是jdk中定义的一个SPI接口,我们以 mysql-connector-java.jar为具体实现类引入classpath下,然后建立连接,看看各个类对应的实际类加载器。

public static void main(String[] args) throws SQLException {
    Connection connection =
            DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?useSSL=false", "root", "123456");

    System.out.println(connection.getClass().getClassLoader());
    System.out.println(connection.getClass().getClassLoader().getParent());
    System.out.println(connection.getClass().getClassLoader().getParent().getParent());
    System.out.println(java.sql.Connection.class.getClassLoader());
    System.out.println(String.class.getClassLoader());
}

输出结果

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@3ab39c39
null
null
null

5、SPI是否有破坏双亲委派模型?

以Connection和mysql-connect-java.jar为例,Bootstrap本身就没有办法加载在classpath下的mysql-connector-java.jar,所以虽然 Connection是定义在rt.jar中,且其本身由Bootstrap ClassLoader加载,但是具体的实现类,Bootstrap ClassLoader是无法加载的。

我的理解是,首先双亲委派模型,本身并不是强制的,双亲委派模型保证了”安全”(越基础的类由越基础的类加载器加载,且只会被加载一次),但是却不够灵活。

如果加载时拿到的class loader是application class loader,那么是没有破坏双亲委派模型的;但是如果拿到的是自定义的class loader,且自定义class loader没有遵守双亲委派模型,那么就SPI就破坏了双亲委派模型。

所以SPI给破坏双亲委派模型留了口子,但是具体有没有破坏,还是要看实际加载的class loader。

另一种看法

当然,也有另一种说法是,SPI中,接口是由Bootstrap ClassLoader 加载的,具体的实现类却是由当前线程上下文的ClassLoader(一般是Application ClassLoader)加载的,而基于双亲委派的 可见性原则(子类加载器可以看到父类加载器,父类加载器看不到子类加载器),SPI 调用方无法看到或拿到具体的实现类的。

双亲委派模型中,class loader的 可见性

6、SPI机制的变种

Spring的自动装配原理

在一个Spring的项目中,肯定会依赖一些其他框架比如:Mybatis,而Spring默认会把当前包及其子包下的bean注入到ioc中,而其他框架由于包名不同,所以不能通过扫描注入,而且注入过程中要尽量与项目解耦,为了解决这一问题,Spring参考了这一SPI机制的设计思想,规定在 classpath目录下 META-INF文件中可以定义 spring.factories文件,这样在项目启动时可以加载所有jar包中的所有 spring.factories文件,将定义的类注入到ioc中,SPI机制在其他框架中也有很多应用,如:Dubbo、Slf4j等

Original: https://www.cnblogs.com/lishanbiaosMark/p/16321668.html
Author: 码出新生活!
Title: 一文读懂SPI机制

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/579726/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球