问题
为什么说spi服务机制破坏了双亲委派模型?
双亲委派机制
- 启动类加载器(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虚拟机第二版》