第一步,加载数据库的驱动
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 implements java.sql.Driver {
2 //
3 // Register ourselves with the DriverManager
4 //
5 static {
6 try {
7 java.sql.DriverManager.registerDriver(new Driver());
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 DriverManager
15 *
16 * @throws SQLException
17 * if a database error occurs.
18 */
19 public Driver() throws SQLException {
20 // Required for Class.forName().newInstance()
21 }
22 }
复制代码
里面的主要逻辑都在父类 NonRegisteringDriver 里实现,而static语句块就做了一件事:生成驱动实例,并向DriverManager注册。所谓注册,就是将driver的信息保存起来,以便后来取用。
第二步,取得数据库连接connection
Connection conn= DriverManager.getConnection(url, user, password);
这里为什么通过DriverManager来取,而不是直接通过生成driver来取???后面马上揭晓!!!
复制代码
1 public static Connection getConnection(String url,
2 String user, String password) throws SQLException {
3 java.util.Properties info = new java.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 static Connection getConnection(
2 String url, java.util.Properties info, Class<?> caller) throws SQLException {
3 /*
4 * When callerCl is null, we should check the application's
5 * (which is invoking this class indirectly)
6 * classloader, so that the JDBC driver class outside rt.jar
7 * 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 then
29 // 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 throw reason;
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 */
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,就不用我们在程序里自己去控制了
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 implements java.sql.Driver {
2 //
3 // Register ourselves with the DriverManager
4 //
5 static {
6 try {
7 java.sql.DriverManager.registerDriver(new Driver());
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 DriverManager
15 *
16 * @throws SQLException
17 * if a database error occurs.
18 */
19 public Driver() throws SQLException {
20 // Required for Class.forName().newInstance()
21 }
22 }
复制代码
里面的主要逻辑都在父类 NonRegisteringDriver 里实现,而static语句块就做了一件事:生成驱动实例,并向DriverManager注册。所谓注册,就是将driver的信息保存起来,以便后来取用。
第二步,取得数据库连接connection
Connection conn= DriverManager.getConnection(url, user, password);
这里为什么通过DriverManager来取,而不是直接通过生成driver来取???后面马上揭晓!!!
复制代码
1 public static Connection getConnection(String url,
2 String user, String password) throws SQLException {
3 java.util.Properties info = new java.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 static Connection getConnection(
2 String url, java.util.Properties info, Class<?> caller) throws SQLException {
3 /*
4 * When callerCl is null, we should check the application's
5 * (which is invoking this class indirectly)
6 * classloader, so that the JDBC driver class outside rt.jar
7 * 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 then
29 // 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 throw reason;
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 */
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,就不用我们在程序里自己去控制了