Java SPI

SPI(Service Provider Interface,服务提供者接口)机制的实现有很多,除Java SPI外还有Spring boot SPI、Dubbo SPI

提示

通过SPI机制可以动态发现接口的实现类并实例化

  • 接口
  • 实现类
  • 配置文件:每个jar包中META-INF/services文件夹下的文件,文件名为接口全路径名,文件中的每一个行都是该接口的实现类的全路径名
  • ServiceLoader类

优点

解耦

缺点

会将接口的所有实现类都加载(见仁见智)

案例分析

  • JDBC中的DriverDriverManager
    java.sql.DriverManager#ensureDriversInitialized,方法注释:通过两种方式发现并加载JDBC驱动,一是系统属性配置,另一个就是通过Java SPI机制来发现和加载
    在这里插入图片描述

    private static void ensureDriversInitialized() {
    
        // 省略部分代码...
        
        // 从系统属性获取JDBC驱动类名,之后会使用Class.forName来加载和初始化驱动类
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty(JDBC_DRIVERS_PROPERTY);
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
    
        // 使用Java SPI机制发现和初始化JDBC驱动
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                
                // 发现:查找META-INF/services/java.sql.Driver中的内容
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
    
                try {
                    while (driversIterator.hasNext()) {
                    
                        /*
                         * 其中使用Class#forName(driverName, false, ClassLoader)获取Class的对象,
                         * initialize为false(加载类但是不初始化类)
                         * 
                         * 然后通过反射获取无参构造函数,执行Constructor#newInstance创建Driver实例(会触发驱动类的初始化)
                         */
                        driversIterator.next();
                    }
                } catch (Throwable t) {
                    // Do nothing
                }
                return null;
            }
        });
        
        // 对系统配置中的驱动类进行加载和初始化
        if (drivers != null && !drivers.equals("")) {
            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);
                }
            }
        }
        
        // ...
    }
    
  • Slf4j利用SPI机制加载不同提供商的日志实现

    slf4j-api在新版本之后不再使用静态绑定(不需要StaticLoggerBinder),而是使用ServiceLoader绑定日志实现
    在这里插入图片描述

    org.slf4j.LoggerFactory#getLogger(java.lang.String)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

扩展

有传言:Java SPI机制破坏了双亲委派机制

  • 对于任意一个类,都需要由加载它的类加载器和这个类本身来一同确立它在JVM中的唯一性

  • 双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器首先并不会直接去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此。因此所有的类加载请求最终都会传送到启动类加载器。只有当父类加载器在其搜索范围内无法找到所需的类,并将结果反馈给子类加载器,子类加载器会尝试自己去加载

  • 每个类加载器都有自己的加载范围

  • Java提供的三个类加载器
    在这里插入图片描述

  • 根据类加载机制,当被加载的类引用了另外一个类的时候,虚拟机就会使用加载第一个类的类加载器来加载被引用的类

    Java SPI机制中使用了线程上下文的类加载器(默认是AppClassLoader在这里插入图片描述

  • ServiceLoader在rt.jar中,由启动类加载器加载(jdk8中成立,jdk9开始去掉了rt.jar)。ServiceLoader触发了接口实现类的加载,但没有使用加载ServiceLoader的类加载器(启动类加载器)去加载接口实现类,而使用AppClassLoader去加载

以上内容似乎支撑了“Java SPI机制破坏了双亲委派机制”的说法,个人不置可否

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值