关于DriverManager与驱动

刚才无意中看到几篇讲DriverManager源码的文章,发现几点没有讲明白的地方。
这里重新说一下:
直接进入正题

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

这个玩意做了这些事情:
1.驱动的实现类:com.mysql.jdbc.Driver 里面的static块,调用DriverManger.registerDriver()来 注册自己
2.DriverManger自己初始化(仅第一次调用注册方法的时候调用一次)
3.把驱动封装成对象存到victor里面

第一点,每个驱动的实现类都必须明确的注册自己到DriverManger里面。
而且这个方法大都是放在static块里面的,所以forName一下就会执行了。

第二点,[color=red]这个初始化并不是初始化com.mysql.jdbc.Driver这个驱动,而是初始化自带的驱动[/color]
如:sun.jdbc.odbc.JdbcOdbcDriver。看代码也能发现,初始化与传递进来的驱动一毛钱关系都没有。

public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
if (!initialized) {
//这里初始化
initialize();
}
....................

最后会调用到

private static void loadInitialDrivers() {
String drivers;
try {
//这里返回的是null
drivers = (String) java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("jdbc.drivers"));
} catch (Exception ex) {
drivers = null;
}
DriverService ds = new DriverService();
//在这里会初始化odbc驱动
java.security.AccessController.doPrivileged(ds);
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null) {
return;
}
}

java.security.AccessController.doPrivileged(ds) 这块代码是native的,搞不清除是干嘛的,于是写了一个代码debug看了一下,程序如下:

public static void main(String[] args) {
DriverManager.setLogWriter(new PrintWriter(System.out));
try {
Class.forName("com.mysql.jdbc.ReplicationDriver");
Class.forName("com.mysql.jdbc.Driver");
Class.forName("org.apache.commons.dbcp.PoolingDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

注册三个驱动程序,并把DriverManger的log输出到控制台,打上断点debug了一下
发现在
java.security.AccessController.doPrivileged(ds)
方法里面,会调用DriverManager.registerDriver,而且传递的参数Driver是sun.jdbc.odbc.JdbcOdbcDriver
所以可以肯定,initialized()方法是用来初始化自带的驱动程序。显然,后面2个驱动的注册,不会再次初始化DriverManager
控制台的输出如下:
[quote]
JdbcOdbcDriver class loaded
registerDriver: driver[className=sun.jdbc.odbc.JdbcOdbcDriver,sun.jdbc.odbc.JdbcOdbcDriver@1b09468]
registerDriver: driver[className=com.mysql.jdbc.Driver,com.mysql.jdbc.Driver@10e790c]
DriverManager.initialize: jdbc.drivers = null
JDBC DriverManager initialized
registerDriver: driver[className=com.mysql.jdbc.NonRegisteringReplicationDriver,com.mysql.jdbc.NonRegisteringReplicationDriver@1551d7f]
registerDriver: driver[className=org.apache.commons.dbcp.PoolingDriver,org.apache.commons.dbcp.PoolingDriver@b8deef]
[/quote]


顺带说一下ClassLoader
驱动的实现类都是用下面这个classLoader来加载的

/* Returns the caller's class loader, or null if none */
private static native ClassLoader getCallerClassLoader();


虽然又是一个native方法,但是顾名思义可以知道,这个方法返回的是调用者的classLoader,如:我在Class A里面调用了DriverManager.getConnection方法,那么驱动类就是由加载A的classLoader来加载。为啥要这样?
看一下DriverManager就明白了。

public static void main(String[] args) {
System.out.println(DriverManager.class.getClassLoader());
}

控制台输出null
表明DriverManager的classLoader是相当NB的bootstrap ClassLoader
而我们自己写的类一般都是由systemClassloader加载的。
如果在DriverManager直接class.forName("com.mysql.jdbc.ReplicationDriver"),就表示要让bootstrap ClassLoader来加载jdk/lib目录下的com.mysql.jdbc.ReplicationDriver,可能么??明显不在那里,而是在classpath目录下。
所以要这么来class.forName("com.mysql.jdbc.ReplicationDriver",true, callerClassLoader)

另外如果getCallerClassLoader()返回了空,则会获取当前线程上下文的classLoader

if(callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}

ContextClassLoader可以设置,默认的都是systemClassLoader
ContextClassLoader在web容器里面用到的比较多,它可以让父classLoader访问到子的classLoader的class,如这个驱动程序
就是,在父classLoader(bootstrap)加载的类里面,访问到了子classLoader(SystemClassLoader)才能加载的类(com.mysql.jdbc.ReplicationDriver)。

你可能感兴趣的:(java基础)