Java中SPI机制的实现详解

 更新时间:2024年01月29日 10:31:53   作者:学而不思则忘  
SPI(Service Provider Interface),是 JDK 内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,下面我们就来看看Java中SPI机制的具体实现

简介

SPI(Service Provider Interface),是 JDK 内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,核心思想是解耦。比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL 和 PostgreSQL 都有不同的实现提供给用户。

当服务提供者提供了一种接口的实现后,需要在 classpath 下的 META-INF/services/ 目录下创建一个以服务接口命名的文件,文件的内容就是接口的具体实现类。

当使用该服务时,扫描 META-INF/services/ 下配置文件,就可以加载类使用该服务。JDK 中查找服务的工具类是:java.util.ServiceLoader

以 MySQL 驱动为例:

源码实现

以上面连接数据库作为示例,看看 java8 SPI 怎么实现的。

入口在 java.sql.DriverManager 类中:

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

上面的代码主要步骤是:

  • 从系统变量中获取有关驱动的定义。
  • 使用 SPI 来获取驱动的实现。
  • 遍历使用 SPI 获取到的具体实现,实例化各个实现类。
  • 根据第一步获取到的驱动列表来实例化具体实现类。

主要关注第2,3步,第 2 步使用 SPI 获取驱动的实现,对应实现:

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

这里没有去 META-INF/services 目录下查找配置文件,也没有加载具体实现类,只是封装了接口类型和类加载器,并初始化了一个迭代器。

接着看第三步,遍历使用 SPI 获取到的具体实现,实例化各个实现类,对应的代码如下:

// 获取迭代器
Iterator<Driver> driversIterator = loadedDrivers.iterator();
// 遍历所有的驱动实现
while(driversIterator.hasNext()) {
    driversIterator.next();
}

在遍历的时候,首先调用driversIterator.hasNext()方法,这里会搜索 classpath 下以及jar包中所有的META-INF/services目录下的java.sql.Driver文件,并找到文件中的实现类的名字,此时并没有实例化具体的实现类。

然后是调用driversIterator.next();方法,此时就会根据驱动名字具体实例化各个实现类了。具体的扫描加载源码见java.util.ServiceLoader 方法。

以上就是Java中SPI机制的实现详解的详细内容,更多关于Java SPI机制的资料请关注脚本之家其它相关文章!

相关文章

  • Spring Boot 启动、停止、重启、状态脚本

    Spring Boot 启动、停止、重启、状态脚本

    今天给大家分享Spring Boot 项目脚本(启动、停止、重启、状态),通过示例代码给大家介绍的非常详细,需要的朋友参考下吧
    2021-06-06
  • Java生成堆内存dump的问题

    Java生成堆内存dump的问题

    这篇文章主要介绍了Java生成堆内存dump的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • Java 实战项目锤炼之小区物业管理系统的实现流程

    Java 实战项目锤炼之小区物业管理系统的实现流程

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用java+SSM+jsp+mysql+maven实现一个小区物业管理系统,大家可以在过程中查缺补漏,提升水平
    2021-11-11
  • Java11中基于嵌套关系的访问控制优化详解

    Java11中基于嵌套关系的访问控制优化详解

    Java(和其他语言)通过内部类支持嵌套类,要使其正常工作,需要编译器执行一些技巧,下面这篇文章主要给大家介绍了关于Java11中基于嵌套关系的访问控制优化的相关资料,需要的朋友可以参考下
    2022-01-01
  • Java类加载器ClassLoader用法解析

    Java类加载器ClassLoader用法解析

    这篇文章主要介绍了Java类加载器ClassLoader用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • java实现简单计算器

    java实现简单计算器

    这篇文章主要为大家详细介绍了java实现简单计算器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • SpringBoot @RequestParam、@PathVaribale、@RequestBody实战案例

    SpringBoot @RequestParam、@PathVaribale、@RequestBody实战案例

    这篇文章主要介绍了SpringBoot @RequestParam、@PathVaribale、@RequestBody实战案例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • 在JPA中criteriabuilder使用or拼接多个like语句

    在JPA中criteriabuilder使用or拼接多个like语句

    这篇文章主要介绍了在JPA中criteriabuilder使用or拼接多个like语句,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • springboot使用nacos的示例详解

    springboot使用nacos的示例详解

    这篇文章主要介绍了springboot使用nacos的示例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • java实现验证码小程序

    java实现验证码小程序

    这篇文章主要为大家详细介绍了java实现验证码小程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-04-04

最新评论