前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >双亲委派机制,懂吧~ 那什么情况下需要破坏它,知道吗?

双亲委派机制,懂吧~ 那什么情况下需要破坏它,知道吗?

原创
作者头像
爪哇缪斯
发布2023-08-19 15:46:32
2150
发布2023-08-19 15:46:32
举报
文章被收录于专栏:爪哇缪斯爪哇缪斯

一、什么是双亲委派机制

我们要获得一个类的Class实例,可以采用如下方式:

那么在Class的forName(className)方法中,会根据是谁调用了Class.forName(className)这个方法,那么就获得当时加载了它的那个ClassLoader,然后,再通过类加载器来负责对类进行加载操作。

获得了类加载器之后,就可以通过如下的loadClass(...)方法对className执行类加载操作的。那么在如下的代码逻辑中,可以看到双亲委派机制的逻辑了。也就是说,如果自己没有加载过className这个类,则必须“委托”给父加载器执行加载操作。除非父类加载器无法加载这个类,才由自己执行加载操作

那么,为什么父类加载器无法加载某个类呢?原因就是,每个类型的加载器z都有其约束的加载路径,如果这个className没有在这个路径下,那么对应的类加载器就无法加载这个类了。如下所示:

二、为什么要破坏双亲委派机制

我们以使用JDBC操作数据库时的代码为例:

代码语言:javascript
复制
public?class?JdbcTest?{
????public?static?void?main(String[]?args)?{
????????Class.forName("com.mysql.jdbc.Driver");
????????Connection?conn?=?DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true",?"root",?"root");
????????...?...
????}
}

【解释】由于是JdbcTest类调用了Class.forName方法,并且JdbcTest类是由AppClassLoader加载的,所以先由AppClassLoader去查找自己是否加载过类"com.mysql.jdbc.Driver",发现没有加载过,那么委托ExtClassLoader去加载,而它之前也没有加载过这个类,那么委托给BootstrapClassLoader去加载,但是由于com.mysql.jdbc.Driver这个类在CLASSPATH中,而不在rt.jarext/*.jar里,所以最终还是会由AppClassLoader去加载。

当"com.mysql.jdbc.Driver"这个类加载后,就会执行它内部的静态方法,将new Driver()实例对象注册到DriverManager中,这样就完成了驱动注册操作。

代码语言:javascript
复制
public?class?Driver?extends?NonRegisteringDriver?implements?java.sql.Driver?{
????public?Driver()?throws?SQLException?{}

????//?静态块执行语句
????static?{
????????try?{
????????????DriverManager.registerDriver(new?Driver());
????????}?catch?(SQLException?var1)?{
????????????throw?new?RuntimeException("Can't?register?driver!");
????????}
????}
}

在JDBC4.0以后,开始支持使用SPI的方式来注册这个Driver,具体做法就是在mysql的jar包中的META-INF/services/java.sql.Driver文件中指明当前使用的Driver是哪个。

那么,我们以往的数据库连接代码就可以改成这样:

代码语言:javascript
复制
public?class?JdbcTest?{
????public?static?void?main(String[]?args)?{
????????//?Class.forName("com.mysql.jdbc.Driver");?//?此处就不需要显示通过Class.forName(...)执行类的加载操作了
????????Connection?conn?=?DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true",?"root",?"root");
????????...?...
????}
}

这个不对啊?Driver在哪加载了呢?我们来看一下DriverManager类(JDK8)的静态方法就知道了。如下所示:

代码语言:javascript
复制
static?{
????loadInitialDrivers();?//?加载初始化驱动器
????println("JDBC?DriverManager?initialized");
}

private?static?void?loadInitialDrivers()?{
????String?drivers;
????try?{
????????drivers?=?AccessController.doPrivileged(new?PrivilegedAction<String>()?{
????????????public?String?run()?{
????????????????return?System.getProperty("jdbc.drivers");
????????????}
????????});
????}?catch?(Exception?ex)?{drivers?=?null;}

????AccessController.doPrivileged(new?PrivilegedAction<Void>()?{
????????public?Void?run()?{
????????????ServiceLoader<Driver>?loadedDrivers?=?ServiceLoader.load(Driver.class);?//?使用SPI加载Driver
????????????Iterator<Driver>?driversIterator?=?loadedDrivers.iterator();
????????????try{
????????????????while(driversIterator.hasNext())?{
????????????????????driversIterator.next();
????????????????}
????????????}?catch(Throwable?t)?{}
????????????return?null;
????????}
????});
????
????if?(drivers?==?null?||?drivers.equals(""))?return;
????
????String[]?driversList?=?drivers.split(":");
????for?(String?aDriver?:?driversList)?{
????????try?{
????????????Class.forName(aDriver,?true,?ClassLoader.getSystemClassLoader());
????????}?catch?(Exception?ex)?{
????????????println("DriverManager.Initialize:?load?failed:?"?+?ex);
????????}
????}
}

【解释】在上面的代码中,我们看到了ServiceLoader.load(Driver.class)这行代码,它是基于SPI的方式去加载了Driver实例了。

那为啥要这么做呢?直接在DriverManager中编写Class.forName("com.mysql.cj.jdbc.Driver")这行代码不就可以了嘛,采用SPI难道是为了炫技嘛?

其实不是的,因为DriverManager这个类的全路径名是java.sql.DriverManager,它是在rt.jar包中的,所以它是由BootstrapClassLoader负责加载的。而com.mysql.cj.jdbc.Driver类不在rt.jar包中,所以BootstrapClassLoader无法加载,而BootstrapClassLoader又是顶层的类加载器了,它没有父加载器,所以既无法委托给父加载器负责加载,而自己又无法加载,那么就只能报错ClassNotFoundException了。

那这个问题怎么解决呢?SPI的出现解决了这个问题,下面我们来看一下SPI是如何处理的,即:

ServiceLoader.load(Driver.class);

那么问题又来了,这个Thread.currentThread().getContextClassLoader()是从当前线程的上下文中获得了ClassLoader,那到底_这个ClassLoader是什么呢?_想要找到答案,我们就需要看Launcher类了,它是负责用来创建类加载器的。

【解释】从上面代码我们可以看到,当前线程的上下文中获得的ClassLoader就是AppClassLoader,那么它加载的类路径就是CLASSPATH,就可以成功的加载"com.mysql.cj.jdbc.Driver"这个类了。它采用的这种方式,就是破坏双亲委托机制来加载Driver类了。

今天的文章内容就这些了:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、什么是双亲委派机制
  • 二、为什么要破坏双亲委派机制
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com