Java SPI机制

前言

虽然我自己在前段时间再总结一些Java知识,但是经过最近的面试发现,很多自己掌握的并不牢靠,所以决定把原来很多内容拆分出来一部分一部分自己写,这篇主要在梳理一遍Java的SPI 机制吧。温故而知新,可以为师矣。


介绍

Java SPI 全程为 Service Provider Interface,直译过来就是 服务提供商接口。我理解的概念的话就是,由JDK语言开发组制定一系列功能接口,但功能的具体实现是由各个服务商自行提供。这也满足的依赖倒置原则。依赖倒置原则的核心就是要我们面向接口编程,理解了面向接口编程。

具体事例

最熟悉的SPI服务应该就是JDBC了

在java.util.sql中定义了对于数据库功能的各个接口,以及数据库操作生命周期中各对象的接口

如Driver表示数据库的驱动器,Connection表示一次数据库的连接

我们在使用第三方实现的时候我们一般是直接通过java.util.sql中的DriverManager来获取具体的第三方Driver或者Connection,那么DriverManager是如何加载到第三方数据库的呢?

接下来我们就好好梳理一下从接口Class文件加载到第三方实现的加载,以及第三方实现的调用的完整流程,看一下DriverManager的源码

环境

jdk1.8.0_144

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

加载SPI第三方实现

    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;
        }
​
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
​
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
        //.... 此处省略一些不重要的内容
​
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
         //关键是这里的Class.forName(aDriver,true,ClassLoader.getSystemClassLoader())
         //此处使用ClassLoader.getSystemClassLoader()直接使用了AppClassLoader来加载具体实现类,打破了双亲委派机制
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

 

再看一下第三方的Driver实现类中都干了些什么,此处以com.mysql.jdbc的Driver为例

   
 static {
        try {
        //将自己的Driver实例注册进java.util.sql.DriverManager的CopyOnWriteArrayList列表中,供后期使用
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

 

当SPI接口调用第三方的功能实现,此处以JDBC的getConnection为例

      try {
            DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","test","123456");
        } catch (SQLException e) {
            e.printStackTrace();
        }

 

    @CallerSensitive
    public static Connection getConnection(String url)
        throws SQLException {
​
        java.util.Properties info = new java.util.Properties();
        //此处返回的是直接调用DriverManager的类,一般为我们自己的程序类
        return (getConnection(url, info, Reflection.getCallerClass()));
    }

 

    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        
        //此处为我们自己程序类的类加载器 一般为AppClassLoader
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
​
        //此处省略一些不重要的内容
​
        for(DriverInfo aDriver : registeredDrivers) {
            
            //由AppClassLoader检查是否已经装载了第三方驱动类
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                   
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
​
            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }
​
        }
        //此处省略一些不重要的内容
       
    }

总结

到目前为止我们已经弄清楚,由BootstrapClassloader加载的协议接口类,如何打破双亲委派机制 来加载AppClassLoader才可以加载到的classpath中的第三方实现类

主要方式为

  1. 使用ClassLoader.getSystemClassLoader来获取AppClassLoader

     Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());
  1. 虽然实际使用中没有用到,使用Thread.currentThread().getContextClassLoader()获取AppClassLoader

    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值