接上篇继续:SPI : Service Provider Interface
ServiceLoader打破双亲委派
我们知道jdk的核心API(e.g rt.jar)是BootstrapClassLoader
加载的,三方提供的jar包是AppClassLoader
加载的,那么ServiceLoader
是rt.jar中的类,那么对应的加载器就是BootstrapClassLoader
。
那么问题来了。
如果一个类由类加载器加载,那么这个类依赖的类也是由相同的类加载器加载的。
很显然,ServiceLoader
这里打破了双亲委派机制。
如何做到的呢?
写一段代码做个测试。
public static void main(String[] args) {
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while (iterator.hasNext()){
Driver driver = iterator.next();
System.out.println("driver:"+driver.getClass());
System.out.println("classLoader:"+driver.getClass().getClassLoader());
}
System.out.println("current thread classLoader:"+Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader loader:"+ServiceLoader.class.getClassLoader());
}
我们在这个项目中引入了MySQL的jar包。执行结果如下:
driver:class com.mysql.cj.jdbc.Driver
classLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
current thread classLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader loader:null
从结果上我们可以看到,已经找到了MySQL的驱动。这里如果再引入Oracl的驱动的话,那么也会打印出来,就不做测试了。这其实就是JDK标准的SPI的一个特性:加载SPI接口的所有实现类。
另外,从执行结果上我们可以看到ServiceLoader
的加载器是BootStrapClassLoader
,因为最后一行输出了null,并且从该类在rt.jar里边也可以说明这一点了。两种不同的classLoader
说明传统的双亲委派确实被打破。
从ServiceLoader.load(Driver.class)
入手找打破双亲委派的方式。
可以看到,第一行获取了当前线程上下文加载器,也就是AppClassLoader
。
第二行做的其实就是通过构造方法将获得的cl
赋值给ServiceLoader
的成员变量loader
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
那么这个成员变量loader
是什么时候用的呢?
我们调用next()
方法的时候,这时候会触发一个类加载,由下边代码可以看到先是在Class.forName
获取class
的时候使用上边的成员变量loader
,然后clazz.newInstance
生成对象。
由此,JDK
通过ServiceLoader
实现的SPI
打破了双亲委派。
尝试几句话说清楚:
加载器是BootStrapClassLoader
的ServiceLoader
,在实例化本身时,将当前线程上上下文加载器赋值给本身的一个成员变量。后续需要实例化SPI接口时,通过这个成员变量去做实例化动作,从而实现了打破双亲委派。