SPI的ClassLoader问题

作者: 小小浪把_Dont_know拍 | 来源:发表于2018-03-25 11:36 被阅读34次

    问题

    为什么说spi服务机制破坏了双亲委派模型?

    双亲委派机制

    类加载器的双亲委派模型
    • 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。
    • 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。
    • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

    JDBC SPI的ClassLoader

    还是上一篇的代码

    public static void main(String[] args)
    {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        Driver driver;
        while (drivers.hasMoreElements())
        {
            driver = drivers.nextElement();
            System.out.println(driver.getClass() + "------" + driver.getClass().getClassLoader());
        }
        System.out.println(DriverManager.class.getClassLoader());
    }
    

    输出结果如下:

    class com.mysql.jdbc.Driver------sun.misc.Launcher$AppClassLoader@2a139a55
    class com.mysql.fabric.jdbc.FabricMySQLDriver------sun.misc.Launcher$AppClassLoader@2a139a55
    null
    

    我们发现DriverManager是null,即通过Bootstrap ClassLoader加载进来的,而com.mysql.jdbc.Driver是通过Application ClassLoader加载进来的。

    由于双亲委派模型,父加载器是拿不到通过子加载器加载的类的。这里就引出了一个问题,为什么通过Bootstrap ClassLoader加载进来的DriverManager,可以拿到Application ClassLoader加载进来的com.mysql.jdbc.Driver?

    这个现象,就是破坏了双亲委派模型。

    为什么要破坏双亲委派模型

    因为DriverManager是通过Bootstrap ClassLoader加载进来的,而com.mysql.jdbc.Driver是通过classpath的JAR包加载进来的。要想通过DriverManager,必须破坏双亲委派模型才能拿到。

    DriverManager是如何拿到com.mysql.jdbc.Driver类的

    通过Thread.currentThread().setContextClassLoader(),将Application ClassLoader设置为线程上下文加载器。在DriverManager类里通过Thread.currentThread().getContextClassLoader()拿到Application ClassLoader进行加载。

    代码见java.util.ServiceLoader

        /**
         * Creates a new service loader for the given service type, using the
         * current thread's {@linkplain java.lang.Thread#getContextClassLoader
         * context class loader}.
         *
         * <p> An invocation of this convenience method of the form
         *
         * <blockquote><pre>
         * ServiceLoader.load(<i>service</i>)</pre></blockquote>
         *
         * is equivalent to
         *
         * <blockquote><pre>
         * ServiceLoader.load(<i>service</i>,
         *                    Thread.currentThread().getContextClassLoader())</pre></blockquote>
         *
         * @param  <S> the class of the service type
         *
         * @param  service
         *         The interface or abstract class representing the service
         *
         * @return A new service loader
         */
        public static <S> ServiceLoader<S> load(Class<S> service) {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            return ServiceLoader.load(service, cl);
        }
    

    SPI加载类有什么优点

    SPI的这种加载方式,就只需要定义好接口,引入符合规范的jar包,就可以获取到实现该接口的类了。

    有点类似于IOC,上层只需要指定一个配置文件路径,在初始化的时候去扫描所有符合的配置文件路径,并解析其中的内容,再去加载对应的类,就可以拿到所有该接口的实现了。

    核心概念:

    • 双亲委派
    • 线程上下文加载器

    参考资料:
    jdbc 类加载器,与 spi 服务机制
    为什么说java spi破坏双亲委派模型?
    为什么spi需要破坏双亲委派模型?
    《深入理解jvm虚拟机第二版》

    相关文章

      网友评论

      本文标题:SPI的ClassLoader问题

      本文链接:https://www.haomeiwen.com/subject/nzkhcftx.html