什么是双亲委派?
如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
双亲委派模型,是一种加载类的约定。这个约定的一个用处是保证安全。比如说你写Java用了String类,你怎么保证你用的那个String类就是JDK里提供的那个String类呢?答案是对于JDK基础类,JDK要用特殊的ClassLoader来保证在正确的位置加载。
JDK主要有3个自带ClassLoader:
最基础:Bootstrap ClassLoader(加载JDK的/lib目录下的类)
次基础:Extension ClassLoader(加载JDK的/lib/ext目录下的类)
普通:Application ClassLoader(程序自己classpath下的类)
加载顺序
子类先委托父类加载
父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类
子类在收到父类无法加载的时候,才会自己去加载
破坏双亲委派(spi)
可以看出双亲委派机制是一种至下而上的加载方式,那么SPI是如何打破这种关系?
以JDBC加载驱动为例:
在JDBC4.0之后支持SPI方式加载java.sql.Driver的实现类。SPI实现方式为,通过ServiceLoader.loadDriver.class)方法,去各自实现Driver接口的lib的META-INF/services/java.sql.Driver文件里找到实现类的名字,通过Thread.currentThread).getContextClassLoader)类加载器加载实现类并返回实例。
驱动加载的过程大致如上,那么是在什么地方打破了双亲委派模型呢?
先看下如果不用Thread.currentThread).getContextClassLoader)加载器加载,整个流程会怎么样。
1、从META-INF/services/java.sql.Driver文件得到实现类名字DriverA
2、Class.forName“xx.xx.DriverA”)来加载实现类
3、Class.forName)方法默认使用当前类的ClassLoader,JDBC是在DriverManager类里调用Driver的,当前类也就是DriverManager,它的加载器是BootstrapClassLoader。
4、用BootstrapClassLoader去加载非rt.jar包里的类xx.xx.DriverA,就会找不到
要加载xx.xx.DriverA需要用到AppClassLoader或其他自定义ClassLoader
5、最终矛盾出现在,要在BootstrapClassLoader加载的类里,调用AppClassLoader去加载实现类
这样就出现了一个问题:如何在父加载器加载的类中,去调用子加载器去加载类?
jdk提供了两种方式,Thread.currentThread).getContextClassLoader)和ClassLoader.getSystemClassLoader)一般都指向AppClassLoader,他们能加载classpath中的类
SPI则用Thread.currentThread).getContextClassLoader)来加载实现类,实现在核心包里的基础类调用用户代码