我们要获得一个类的Class实例,可以采用如下方式:
那么在Class的forName(className)
方法中,会根据是谁调用了Class.forName(className)这个方法,那么就获得当时加载了它的那个ClassLoader,然后,再通过类加载器来负责对类进行加载操作。
获得了类加载器之后,就可以通过如下的loadClass(...)
方法对className执行类加载操作的。那么在如下的代码逻辑中,可以看到双亲委派机制的逻辑了。也就是说,如果自己没有加载过className这个类,则必须“委托”给父加载器执行加载操作。除非父类加载器无法加载这个类,才由自己执行加载操作。
那么,为什么父类加载器无法加载某个类呢?原因就是,每个类型的加载器z都有其约束的加载路径,如果这个className
没有在这个路径下,那么对应的类加载器就无法加载这个类了。如下所示:
我们以使用JDBC操作数据库时的代码为例:
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.jar
和ext/*.jar
里,所以最终还是会由AppClassLoader去加载。
当"com.mysql.jdbc.Driver
"这个类加载后,就会执行它内部的静态方法,将new Driver()实例对象注册到DriverManager中,这样就完成了驱动注册操作。
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是哪个。
那么,我们以往的数据库连接代码就可以改成这样:
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)的静态方法就知道了。如下所示:
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 删除。