ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

一文读懂SPI机制

2022-05-28 19:32:56  阅读:193  来源: 互联网

标签:一文 读懂 实现 loader SPI ServiceLoader class 加载


一文读懂SPI机制

目录

1、问题

什么是SPI?

2、答案

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

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

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

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

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

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

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

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

如果同一个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<Driver> 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);
    }

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等

标签:一文,读懂,实现,loader,SPI,ServiceLoader,class,加载
来源: https://www.cnblogs.com/lishanbiaosMark/p/16321668.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有