SPI的ClassLoader问题

问题

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

双亲委派机制

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

JDBC SPI的ClassLoader

还是上一篇的代码

public static void main(String[] args)
{
    Enumeration 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}.
     *
     * 

An invocation of this convenience method of the form * *

     * ServiceLoader.load(service)
* * is equivalent to * *
     * ServiceLoader.load(service,
     *                    Thread.currentThread().getContextClassLoader())
* * @param the class of the service type * * @param service * The interface or abstract class representing the service * * @return A new service loader */ public static ServiceLoader load(Class service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }

SPI加载类有什么优点

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

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

核心概念:

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

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

你可能感兴趣的:(SPI的ClassLoader问题)