mysql驱动类加载方式_jdbc驱动的类加载过程

这段时间跟类加载机制是干上了。

这一篇来分析一下jdbc工作过程中涉及到的类加载流程,重点是想看看在双亲委派模型不适用的时候,如何解决。

第一步,加载数据库的驱动

Class.forName("oracle.jdbc.driver.OracleDriver")

Class.forName("com.mysql.jdbc.Driver")

Class.forName 方法会根据类的全路径名称去加载对应的class文件,生成类型,并初始化类型。也就是说static语句块会执行。

下面来看看 com.mysql.jdbc.Driver 类

1 public class Driver extends NonRegisteringDriver implementsjava.sql.Driver {2 //

3 //Register ourselves with the DriverManager4 //5 static{6 try{7 java.sql.DriverManager.registerDriver(newDriver());8 } catch(SQLException E) {9 throw new RuntimeException("Can't register driver!");10 }11 }12

13 /**

14 * Construct a new driver and register it with DriverManager15 *16 *@throwsSQLException17 * if a database error occurs.18 */

19 public Driver() throwsSQLException {20 //Required for Class.forName().newInstance()

21 }22 }

里面的主要逻辑都在父类 NonRegisteringDriver 里实现,而static语句块就做了一件事:生成驱动实例,并向DriverManager注册。所谓注册,就是将driver的信息保存起来,以便后来取用。

第二步,取得数据库连接connection

Connection conn= DriverManager.getConnection(url, user, password);

这里为什么通过DriverManager来取,而不是直接通过生成driver来取???后面马上揭晓!!!

1 public staticConnection getConnection(String url,2 String user, String password) throwsSQLException {3 java.util.Properties info = newjava.util.Properties();4

5 if (user != null) {6 info.put("user", user);7 }8 if (password != null) {9 info.put("password", password);10 }11

12 return(getConnection(url, info, Reflection.getCallerClass()));13 }

Reflection.getCallerClass() 是取得调用类,这个方法是native的。我这里是jdk1.8,以前的版本不是调用的这个方法,如果感兴趣也可以看看。

1 private staticConnection getConnection(2 String url, java.util.Properties info, Class> caller) throwsSQLException {3 /*

4 * When callerCl is null, we should check the application's5 * (which is invoking this class indirectly)6 * classloader, so that the JDBC driver class outside rt.jar7 * can be loaded from here.8 */

9 ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;10 synchronized(DriverManager.class) {11 //synchronize loading of the correct classloader.

12 if (callerCL == null) {13 callerCL =Thread.currentThread().getContextClassLoader();14 }15 }16

17 if(url == null) {18 throw new SQLException("The url cannot be null", "08001");19 }20

21 println("DriverManager.getConnection(\"" + url + "\")");22

23 //Walk through the loaded registeredDrivers attempting to make a connection.24 //Remember the first exception that gets raised so we can reraise it.

25 SQLException reason = null;26

27 for(DriverInfo aDriver : registeredDrivers) {28 //If the caller does not have permission to load the driver then29 //skip it.

30 if(isDriverAllowed(aDriver.driver, callerCL)) {31 try{32 println(" trying " +aDriver.driver.getClass().getName());33 Connection con =aDriver.driver.connect(url, info);34 if (con != null) {35 //Success!

36 println("getConnection returning " +aDriver.driver.getClass().getName());37 return(con);38 }39 } catch(SQLException ex) {40 if (reason == null) {41 reason =ex;42 }43 }44

45 } else{46 println(" skipping: " +aDriver.getClass().getName());47 }48

49 }50

51 //if we got here nobody could connect.

52 if (reason != null) {53 println("getConnection failed: " +reason);54 throwreason;55 }56

57 println("getConnection: no suitable driver found for "+url);58 throw new SQLException("No suitable driver found for "+ url, "08001");59 }

这个方法一开始就要得到调用类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*/

privateClassLoader contextClassLoader;//这段if---else代码出自init方法

if (security == null ||isCCLOverridden(parent.getClass()))this.contextClassLoader =parent.getContextClassLoader();else

this.contextClassLoader =parent.contextClassLoader;publicClassLoader getContextClassLoader() {if (contextClassLoader == null)return null;

SecurityManager sm=System.getSecurityManager();if (sm != null) {

ClassLoader.checkClassLoaderPermission(contextClassLoader,

Reflection.getCallerClass());

}returncontextClassLoader;

}public voidsetContextClassLoader(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,就不用我们在程序里自己去控制了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值