java的双亲委派

什么是双亲委派

双亲委派

如果一个类加载器收到某个类的加载请求,则该加载器不会去加载该类,而是把这个请求给父类加载器,每个一个层次的加载器都是如此,因为所有的类加载器请求最终都会传到顶端的启动类加载器,只有父类在加载器范围内找不到该类时,会将结果返回给子类加载器,最终会找到可以加载该子类的子类加载器

双亲委派的作用:保证JDK的核心类优先加载

如果一个类可以委托给最基础的classloader加载,就不能让高层的classloader进行加载

双亲委派流程:

子类先委托父类加载;父类加载器有自己的范围,在自己的范围中找不到则不加载,返回给子类加载器;子类加载器收到福类加载器无法加载的消息,则自己下载

顺便介绍一下三种类加载器

  1. 启动类加载器:C++实现,在jav中无法获取,负责加载JAVA_HOME/lib下的类
  2. 拓展类加载器:JAVA实现,在java里获取,负责加载JAVA_HOME/lib/ext下的类
  3. 系统类加载器/应用程序加载器:简单来说,我们写的代码默认就是由他来进行加载(程序自己classpath下的类)ClassLoader.gerSystemClassloader

向上委派:当类加载器加载一个类时,首先会向他的父类进行委派,知道启动类加载器

向下委派:当启动类加载器发现无法加载这个类时,会委派给他的子类进行加载,如果知道最后的一个子类还不能加载,就跑出classnotfound异常

打破双亲委派

为什么要打破?
举一个最常见的栗子:我们常用数据库驱动Driver接口,Driver定义在jdk当中,当其实现却是各个数据库服务商,例如,mysql的MYSQL CONNECROR,所有这就有个问题,DriverManger要加载各个Driver接口实现类,然后进行管理,但是DriverManager是由启动类加载器进行加载的,而这个启动类加载器默认值加载JAVA_HOME下面的lib,但我们真正要加载的是各个实现类,需要有系统类加载器进行加载,这个时候就需要启动类加载器委托系统类加载器去加载Driver实现类,从而破坏了双亲委派。

打破方式:

自定义类加载,重写loadclass方法

因为双亲委派机制的实现都是通过这个方法实现的,这个方法可以指定类通过什么加载器来进行加载,所以如果我们改写他的加载规则,就相当于打破了双亲委派机制。默认的过程是这样的,先判断这个类是不是已经被当前层的类加载器加载过了,如果没有加载过就就将该类委派给父类加载器,如果父加载器无法加载再向下传递,回来由自己来进行加载,重写了这个方法以后就能自己定义使用什么加载器了,也可以自定义加载委派机制,也就打破了双亲委派模型

使用线程上下文类

线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。有了线程上下文加载器,JNDI服务就可以使用它去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

Last modification:December 22, 2022
如果觉得我的文章对你有用,请随意赞赏