jdbc驱动的类加载过程
这段时间跟类加载机制是干上了。
这一篇来分析一下jdbc工作过程中涉及到的类加载流程,重点是想看看在双亲委派模型不适用的时候,如何解决。
第一步,加载数据库的驱动
Class.forName("oracle.jdbc.driver.OracleDriver")
Class.forName("com.mysql.jdbc.Driver")
Class.forName 方法会根据类的全路径名称去加载对应的class文件,生成类型,并初始化类型。也就是说static语句块会执行。
下面来看看 com.mysql.jdbc.Driver 类
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
里面的主要逻辑都在父类 NonRegisteringDriver 里实现,而static语句块就做了一件事:生成驱动实例,并向DriverManager注册。所谓注册,就是将driver的信息保存起来,以便后来取用。
第二步,取得数据库连接connection
Connection conn= DriverManager.getConnection(url, user, password);
这里为什么通过DriverManager来取,而不是直接通过生成driver来取???后面马上揭晓!!!
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties(); if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
} return (getConnection(url, info, Reflection.getCallerClass()));
}
Reflection.getCallerClass() 是取得调用类,这个方法是native的。我这里是jdk1.8,以前的版本不是调用的这个方法,如果感兴趣也可以看看。
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
} if(url == null) {
throw new SQLException("The url cannot be null", "08001");
} println("DriverManager.getConnection(\"" + url + "\")"); // Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null; for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
} } else {
println(" skipping: " + aDriver.getClass().getName());
} } // if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
} println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
这个方法一开始就要得到调用类caller的类加载器callerCL,为的是后面再去加载数据库的driver,做一下验证,
具体在 isDriverAllowed(aDriver.driver, callerCL) 里面的代码里。
那问题来了,为什么就不能用加载DriverManager的类加载器呢???
因为DriverManager在rt.jar里面,它的类加载器上启动类加载器。而数据库的driver(com.mysql.jdbc.Driver)是放在classpath里面的,启动类加载器是不能加载的。所以,如果严格按照双亲委派模型,是没办法解决的。而这里的解决办法是:通过调用类的类加载器去加载。而如果调用类的加载器是null,就设置为线程的上下文类加载器:
Thread.currentThread().getContextClassLoader()
好的,下面通过Thread类的源码,分析线程的上下文类加载器。
/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader; // 这段if---else代码出自init方法
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader; public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,
Reflection.getCallerClass());
}
return contextClassLoader;
} public void setContextClassLoader(ClassLoader cl) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
contextClassLoader = cl;
}
init方法里面代码的逻辑是:把父线程的上下文类加载器给继承过来。这里的父子关系是指谁启动谁的关系,比如在线程A里面启动了线程B,那B线程的父线程就是A。
既然都是一路继承,那第一个启动的线程(包含main方法的那个线程)里面的contextClassLoader是谁设置的呢???
这就要看 sun.misc.Launcher 这个类的源码。Launcher是JRE中用于启动程序入口main()的类。
loader = AppClassLoader.getAppClassLoader(extcl); Thread.currentThread().setContextClassLoader(loader);
这里截取的两行代码出自 Launcher 的构造方法。第一行用一个扩展类加载器extcl构造了一个系统类加载器loader,第二行把loader设置为当前线程(包含main方法)的类加载器。所以,我们启动一个线程的时候,如果之前都没有调用 setContextClassLoader 方法明确指定的话,默认的就是系统类加载器。
到这里,整个加载流程基本上一目了然了。
现在,再回到之前 DriverManager的getConnection 方法,好像还有一个疑问没有解决。
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
最后返回一个connection,但是在一个循环里面。也就是说,当我们注册了多个数据库驱动,mysql,oracle等;DriverManager都帮我们管理了,它会取出一个符合条件的driver,就不用我们在程序里自己去控制了。
jdbc驱动的类加载过程的更多相关文章
- JVM总括四-类加载过程、双亲委派模型、对象实例化过程
JVM总括四-类加载过程.双亲委派模型.对象实例化过程 目录:JVM总括:目录 一. 类加载过程 类加载过程就是将.class文件转化为Class对象,类实例化的过程,(User user = new ...
- JDBC驱动自身问题引发的FullGC
公众号HelloJava刊出一篇<MySQL Statement cancellation timer 故障排查分享>,作者的某服务的线上机器报 502(502是 nginx 做后端健康检 ...
- Confluence 6 数据库 JDBC 驱动
本页面提供了支持的数据库的所有 JDBC 驱动下载链接. 基于许可证的原因,我们没有将 MySQL 或 Oracle 的数据库驱动整合到 Confluence 中,因此你需要在 Confluence ...
- 注意JDBC驱动的版本和JDK的版本是否匹配 JDBC连接Mariadb
Java利用JDBC连接Mariadb的过程和MySQL基本一致. 但是需要注意JDBC驱动的版本和JDK的版本是否匹配: JDBC和JDK版本对应关系 JDBC版本 JDK版本 2.x 1.8 1. ...
- java类加载过程以及双亲委派机制
前言:最近两个月公司实行了996上班制,加上了熬了两个通宵上线,状态很不好,头疼.牙疼,一直没有时间和精力写博客,也害怕在这样的状态下写出来的东西出错.为了不让自己荒废学习的劲头和习惯,今天周日,也打 ...
- IntelliJ IDEA+Mysql connecter/j JDBC驱动连接
在IntelliJ IDEA中用connecter/j jdbc驱动连接MYSQL 以下是解决过程,待整合...有点懒,有空再改 官方文档:https://www.cnblogs.com/cn-chy ...
- (转)MySQL的JDBC驱动源码解析
一.背景 MySQL是一个中小型关系型数据库管理系统,目前我们淘宝也使用的也非常广泛.为了对开发中间DAO持久层的问题能有更深的理解以及最近在使用的phoenix on Hbase的SQL也是实现的J ...
- java加载jdbc驱动三种方式的比较
一.引言 平时连接数据库的时候首先要加载jdbc驱动,这一步骤其实有三种方式,他们的区别?优劣? 二.快速了解三种加载方式 Class.forName(“com.mysql.jdbc.Driver”) ...
- jdbc获取数据具体过程
下面是个最简单的使用jdbc取得数据的应用.在例子之后我将分成4步,分别是①取得连接,②创建PreparedStatement,③设置参数,④执行查询,来分步分析这个过程.除了设置参数那一步之外,其他 ...
随机推荐
- webpack2.x基础属性讲解(一)
webpack作为构建工具平时作为前端作为优化.模块编程.和分片打包的重要组成部分,大家可能并不陌生,如果没有时刻的去关注文档,那么大家可能不太清楚webpack已经默默然的升级到2.x了,对比1 ...
- 给自己的 MAC 添加一个桌面日历
使用 Ubuntu 做自己的办公环境用了将近三年,最近换了新款的 MBP,系统都用的很舒服. 不过 Ubuntu 是在我的 TP W540上部署的,而 W540 + 电源适配太重了(我的电池是9芯的) ...
- POPTEST老李分享session,cookie的安全性以及区别 2
四,session和cookie谁更安全 就个人而言,我觉得session更安全一点,我以下几点看法. 1,如果session和cookie一样安全的话,二者就没有并要同时存在了,只要cookie就好 ...
- Xamarin+Prism开发之.net standard化
前面[Visual Studio 2017创建.net standard类库编译出错原因]已经解决.net standard类库的编译问题,现在可以正式进入主题了.作为.net 跨平台开发者也得跟上时 ...
- 以正确的姿势实现一棵JavaScript菜单树
菜单树是常见的前端特效, 一般长下面这样 还有各种形态的变种, 有长这样的 也有长这样的 尽管这些菜单的相貌都不尽相同, 在功能实现的本质上却都是相同的.实现程序的大致流程如下 读取服务器端的菜单数据 ...
- 学习MVC之租房网站(二)-框架搭建及准备工作
在上一篇<学习MVC之租房网站(一)-项目概况>中,确定了UI+Service的“双层”架构,并据此建立了项目 接下来要编写Common类库.配置AdminWeb和FrontWeb 一.编 ...
- python的MySQLdb模块在linux环境下的安装
开始学习python数据库编程后,在了解了基本概念,打算上手试验一下时,卡在了MYSQLdb包的安装上,折腾了半天才解决.记录一下我在linux中安装此包遇到的问题.系统是ubuntn15.04. 1 ...
- es6 module + webpack
其实在之前本人就看了 es6 里面的一部分内容,当然是阮一峰大神的 ECMAScript 6 入门. 最近闲来无事又来看下,其中 Module 的语法 这章时候,用里面代码跑的时候,理所当然的报错 S ...
- 使用 Http 的 Get 方式读取网络数据
作为移动平台的应用,一定避免不了与网络交换数据,不论是读取网页数据,还是调用API接口,都必须掌握Http通信技术 代码如下: package zw1; import java.io.Buffered ...
- 装饰器模式(Decorator)——深入理解与实战应用
本文为原创博文,转载请注明出处,侵权必究! 1.初识装饰器模式 装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能.其结构图如下: Component为统一接口,也是装饰类和被装 ...