简单介绍ClassLoader的双亲委派机制:
java类通过Classloader加载,系统默认的3个Classloader之间有引用关系,AppClassLoader的parent属性引用了ExtClassloader,ExtClassloader的parent为null,代表引用了BootstrapClassloader。在类加载时,子加载器会调用父加载器来加载类,如果父加载器不能加载类,才会交给子加载器来加载;如果子加载器也加载失败,那么就报异常。
可以看出双亲委派机制是一种至下而上的加载方式,那么SPI是如何打破这种关系?
以JDBC加载驱动为例:
在JDBC4.0之后支持SPI方式加载java.sql.Driver的实现类。SPI实现方式为,通过ServiceLoader.load(Driver.class)方法,去各自实现Driver接口的lib的META-INF/services/java.sql.Driver文件里找到实现类的名字,通过Thread.currentThread().getContextClassLoader()类加载器加载实现类并返回实例。
驱动加载的过程大致如上,那么是在什么地方打破了双亲委派模型呢?
先看下如果不用Thread.currentThread().getContextClassLoader()加载器加载,整个流程会怎么样。
- 从META-INF/services/java.sql.Driver文件得到实现类名字DriverA
- Class.forName("xx.xx.DriverA")来加载实现类
- Class.forName()方法默认使用当前类的ClassLoader,JDBC是在DriverManager类里调用Driver的,当前类也就是DriverManager,它的加载器是BootstrapClassLoader。
- 用BootstrapClassLoader去加载非rt.jar包里的类xx.xx.DriverA,就会找不到
- 要加载xx.xx.DriverA需要用到AppClassLoader或其他自定义ClassLoader
- 最终矛盾出现在,要在BootstrapClassLoader加载的类里,调用AppClassLoader去加载实现类
这样就出现了一个问题:如何在父加载器加载的类中,去调用子加载器去加载类?
- jdk提供了两种方式,Thread.currentThread().getContextClassLoader()和ClassLoader.getSystemClassLoader()一般都指向AppClassLoader,他们能加载classpath中的类
- SPI则用Thread.currentThread().getContextClassLoader()来加载实现类,实现在核心包里的基础类调用用户代码