其他分享
首页 > 其他分享> > 一文读懂SPI机制

一文读懂SPI机制

作者:互联网

一文读懂SPI机制

目录

1、问题

什么是SPI?

2、答案

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

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