首先我们要知道双亲委派机制是为了解决什么问题?
有关类加载器,可以参考我的这篇博客:
双亲委派
所谓的双亲委派,就是先让父亲加载器试图加载该Class,只有在父亲加载器无法加载该类时才尝试从自己的类路径中加载该类。
通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父亲加载器,依次递归,如果父亲加载器可以完成类加载任务,就成功返回;
只有父亲加载器无法完成此加载任务时,才自己去加载。
这样所有的类都会首先传递到最上层的Bootstrap ClassLoader,只有父亲加载器无法完成加载,那么此时儿子加载器才会自己去尝试加载。
这里注意,我没有用父类加载器、子类加载器这样的语句,而是使用了父亲加载器,因为上图中这些箭头并不表示继承关系,而是一种逻辑关系, 实际上是通过组合的方式来实现的,这也是很多博客上没有写清楚,容易误导人的一点。
通过源码来看一下双亲委派具体是怎么实现的
缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。
这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。
parent变量代表了当前class loader 的父亲加载器,这里就体现了不是通过继承,而是通过组合的方式实现类加载器之间的父子关系。
如果parent等于null,那这里要说一下,当parent等于null 代表parent 为bootstrap classloader 。我们开头也讲过,bootstrap classloader是由jvm内部实现的,没有办法被程序引用,所以这里约定为null。当parent为null,就调用findBootstrapClassOrNull这个方法,让BootstrapClassLoader尝试进行加载,如果parent不为null 就让parent根据类的全限定名尝试加载,并返回该类。如果返回的class为null 说明parent加载不了,使用当前的loader的findclass方法尝试加载这个全限定名的class,需要类加载器自己去实现。
打破双亲委派机制
在大部分情况下,双亲委派机制是能够生效并且是能按预期执行的。
1第一次被破坏
本图取自咕泡学院,如有侵权,联系速删
除非是有特殊的业务场景,一般来说不要主动去破坏双亲委派模型
那有的人可能会有疑问啦,既然jvm推荐并希望开发者遵循双亲尾派模型,那么为什么不把load class方法像defineClass设定成final来修饰?
java.lang.ClassLoader 的load class方法呢在java很早的版本就有了,而双亲委派模型是在jdk1.2引入的特性。
java是向下兼容的,也就是说引入双擎委派机制时呢,世界上已经存在了很多像我上面一样的代码,那么jvm只能向下兼容的,只能提出折中的解决方式。
这个解决的措施就是在Jdk1.2后,引入了findclass方法,推荐用户去重写该方法,而不是直接重写 Load class方法,这样呢就依然能够符合双亲委派模型。
这就是史上第一次的双亲委派模型被破坏了,像很多事情(*装)只有零次和N次,双亲委派模型第二次被破坏,是由于这个模型自身的缺陷导致,双亲委派能很好的解决了各个类加载器协作时基础类型的一致性问题,但是如果有基础类型要调用用户的代码,这又该怎么办呢?
第二次被破坏
比如说JDK想要提供操作数据库的功能,那么数据库有很多种,并且随着时间的推移将会出现各种品类的数据库,想要JDK需要针对不同的数据库和具体代码都一一实现,这不现实,jdk也不知道各种品类数据里具体的实现方式,那么比较合理的方式就是JDK提供一组规范和接口,各个不同数据库厂商按照这个接口去实现自己的类库。
SPI 怎么做呢
为了解决这个困境,Java的设计团队只好引入了一个不太优雅的设计:线程上下文类加载器 (Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContext-ClassLoader()方 法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
有了线程上下文类加载器,程序就可以做一些不符合双亲委派模型的事情了。JNDI服务使用这个线程上下文类,加载器去加载所需的SPI服务代码,这是一种父类加载器去请求子类加载器完成类加载的行为,这种行为实际上是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则,但也是无可奈何的事情。
Java中涉及SPI的加载基本上都采用这种方式来完成,例如JNDI、 JDBC、JCE、JAXB和JBI等。不过,当SPI的服务提供者多于一个的时候,代码就只能根据具体提供者的类型来硬编码判断,为了消除这种极不优雅的实现方式,在JDK 6时,JDK提供了java.util.ServiceLoader类,以META-INF/services中的配置信息,辅以责任链模式,这才算是给SPI的加 载提供了一种相对合理的解决方案。
Spring Boot 当中的SPI机制也是在JDK的机制之上去完成的,实现的思路是一样的,只不过会比JDK的实现更加优雅。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/203445.html原文链接:https://javaforall.net
