参见如下简单的程序
package db; import java.sql.*; public class DBTest { private static final String USERNAME = "root"; private static final String PASSWD = "root"; private static final String DATABASE = "test"; private static final String DBMS = "mysql"; private static final String HOST = "localhost"; private static final String PORT = "3306"; private static final String DSN = "jdbc:" + DBMS + "://" + HOST + ":" + PORT + "/" + DATABASE; public static void main(String[] args) { try { Connection conn = DriverManager.getConnection(DSN, USERNAME, PASSWD); String query = "SELECT * FROM user"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(query); while (rs.next()) { System.out.println(rs.getInt(1) + " " + rs.getString(2) + " " + rs.getInt(3)); } } catch (SQLException e) { e.printStackTrace(); } } }
下面我们来分析 DriverManager 的这个方法:
public static Connection getConnection(String url, String user, String password) throws SQLException
查看一下DriverManager源码,代码块我按执行步骤全部贴出来:
1. 调用getConnection()方法
1 /** 2 * Attempts to establish a connection to the given database URL. 3 * The <code>DriverManager</code> attempts to select an appropriate driver from 4 * the set of registered JDBC drivers. 5 * 6 * @param url a database url of the form 7 * <code>jdbc:<em>subprotocol</em>:<em>subname</em></code> 8 * @param user the database user on whose behalf the connection is being 9 * made 10 * @param password the user's password 11 * @return a connection to the URL 12 * @exception SQLException if a database access error occurs 13 */ 14 public static Connection getConnection(String url, 15 String user, String password) throws SQLException { 16 java.util.Properties info = new java.util.Properties(); 17 18 // Gets the classloader of the code that called this method, may 19 // be null. 20 ClassLoader callerCL = DriverManager.getCallerClassLoader(); 21 22 if (user != null) { 23 info.put("user", user); 24 } 25 if (password != null) { 26 info.put("password", password); 27 } 28 29 return (getConnection(url, info, callerCL)); 30 }
2. 调用实际起作用的getConnection()方法
1 // Worker method called by the public getConnection() methods. 2 private static Connection getConnection( 3 String url, java.util.Properties info, ClassLoader callerCL) throws SQLException { 4 java.util.Vector drivers = null; 5 /* 6 * When callerCl is null, we should check the application's 7 * (which is invoking this class indirectly) 8 * classloader, so that the JDBC driver class outside rt.jar 9 * can be loaded from here. 10 */ 11 synchronized(DriverManager.class) { 12 // synchronize loading of the correct classloader. 13 if(callerCL == null) { 14 callerCL = Thread.currentThread().getContextClassLoader(); 15 } 16 } 17 18 if(url == null) { 19 throw new SQLException("The url cannot be null", "08001"); 20 } 21 22 println("DriverManager.getConnection(\"" + url + "\")"); 23 24 if (!initialized) { 25 initialize(); 26 } 27 28 synchronized (DriverManager.class){ 29 // use the readcopy of drivers 30 drivers = readDrivers; 31 } 32 33 // Walk through the loaded drivers attempting to make a connection. 34 // Remember the first exception that gets raised so we can reraise it. 35 SQLException reason = null; 36 for (int i = 0; i < drivers.size(); i++) { 37 DriverInfo di = (DriverInfo)drivers.elementAt(i); 38 39 // If the caller does not have permission to load the driver then 40 // skip it. 41 if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) { 42 println(" skipping: " + di); 43 continue; 44 } 45 try { 46 println(" trying " + di); 47 Connection result = di.driver.connect(url, info); 48 if (result != null) { 49 // Success! 50 println("getConnection returning " + di); 51 return (result); 52 } 53 } catch (SQLException ex) { 54 if (reason == null) { 55 reason = ex; 56 } 57 } 58 } 59 60 // if we got here nobody could connect. 61 if (reason != null) { 62 println("getConnection failed: " + reason); 63 throw reason; 64 } 65 66 println("getConnection: no suitable driver found for "+ url); 67 throw new SQLException("No suitable driver found for "+ url, "08001"); 68 }
这里有几个比较重要的地方,一个L25的initialize()方法,下面是他的源码
1 // Class initialization. 2 static void initialize() { 3 if (initialized) { 4 return; 5 } 6 initialized = true; 7 loadInitialDrivers(); 8 println("JDBC DriverManager initialized"); 9 } 10 11 private static void loadInitialDrivers() { 12 String drivers; 13 14 try { 15 drivers = (String) java.security.AccessController.doPrivileged( 16 new sun.security.action.GetPropertyAction("jdbc.drivers")); 17 } catch (Exception ex) { 18 drivers = null; 19 } 20 21 // If the driver is packaged as a Service Provider, 22 // load it. 23 24 // Get all the drivers through the classloader 25 // exposed as a java.sql.Driver.class service. 26 27 DriverService ds = new DriverService(); 28 29 // Have all the privileges to get all the 30 // implementation of java.sql.Driver 31 java.security.AccessController.doPrivileged(ds); 32 33 println("DriverManager.initialize: jdbc.drivers = " + drivers); 34 if (drivers == null) { 35 return; 36 } 37 while (drivers.length() != 0) { 38 int x = drivers.indexOf(':'); 39 String driver; 40 if (x < 0) { 41 driver = drivers; 42 drivers = ""; 43 } else { 44 driver = drivers.substring(0, x); 45 drivers = drivers.substring(x+1); 46 } 47 if (driver.length() == 0) { 48 continue; 49 } 50 try { 51 println("DriverManager.Initialize: loading " + driver); 52 Class.forName(driver, true, 53 ClassLoader.getSystemClassLoader()); 54 } catch (Exception ex) { 55 println("DriverManager.Initialize: load failed: " + ex); 56 } 57 } 58 }
这一段就是加载数据库驱动的地方,以我用的connector/j为例,看L27,这个DriverService是一个内部类,代码如下:
1 // DriverService is a package-private support class. 2 class DriverService implements java.security.PrivilegedAction { 3 Iterator ps = null; 4 public DriverService() {}; 5 public Object run() { 6 7 // uncomment the followin line before mustang integration 8 // Service s = Service.lookup(java.sql.Driver.class); 9 // ps = s.iterator(); 10 11 ps = Service.providers(java.sql.Driver.class); 12 13 /* Load these drivers, so that they can be instantiated. 14 * It may be the case that the driver class may not be there 15 * i.e. there may be a packaged driver with the service class 16 * as implementation of java.sql.Driver but the actual class 17 * may be missing. In that case a sun.misc.ServiceConfigurationError 18 * will be thrown at runtime by the VM trying to locate 19 * and load the service. 20 * 21 * Adding a try catch block to catch those runtime errors 22 * if driver not available in classpath but it's 23 * packaged as service and that service is there in classpath. 24 */ 25 26 try { 27 while (ps.hasNext()) { 28 ps.next(); 29 } // end while 30 } catch(Throwable t) { 31 // Do nothing 32 } 33 return null; 34 } //end run 35 36 } //end DriverService
L11的 sun.misc.Service.providers()方法是关键所在,代码如下
1 /** 2 * Locates and incrementally instantiates the available providers of a 3 * given service using the given class loader. 4 * 5 * <p> This method transforms the name of the given service class into a 6 * provider-configuration filename as described above and then uses the 7 * <tt>getResources</tt> method of the given class loader to find all 8 * available files with that name. These files are then read and parsed to 9 * produce a list of provider-class names. The iterator that is returned 10 * uses the given class loader to lookup and then instantiate each element 11 * of the list. 12 * 13 * <p> Because it is possible for extensions to be installed into a running 14 * Java virtual machine, this method may return different results each time 15 * it is invoked. <p> 16 * 17 * @param service 18 * The service's abstract service class 19 * 20 * @param loader 21 * The class loader to be used to load provider-configuration files 22 * and instantiate provider classes, or <tt>null</tt> if the system 23 * class loader (or, failing that the bootstrap class loader) is to 24 * be used 25 * 26 * @return An <tt>Iterator</tt> that yields provider objects for the given 27 * service, in some arbitrary order. The iterator will throw a 28 * <tt>ServiceConfigurationError</tt> if a provider-configuration 29 * file violates the specified format or if a provider class cannot 30 * be found and instantiated. 31 * 32 * @throws ServiceConfigurationError 33 * If a provider-configuration file violates the specified format 34 * or names a provider class that cannot be found and instantiated 35 * 36 * @see #providers(java.lang.Class) 37 * @see #installedProviders(java.lang.Class) 38 */ 39 public static Iterator providers(Class service, ClassLoader loader) 40 throws ServiceConfigurationError 41 { 42 return new LazyIterator(service, loader); 43 } 44 45 /** 46 * Private inner class implementing fully-lazy provider lookup 47 */ 48 private static class LazyIterator implements Iterator { 49 50 Class service; 51 ClassLoader loader; 52 Enumeration configs = null; 53 Iterator pending = null; 54 Set returned = new TreeSet(); 55 String nextName = null; 56 57 private LazyIterator(Class service, ClassLoader loader) { 58 this.service = service; 59 this.loader = loader; 60 } 61 62 public boolean hasNext() throws ServiceConfigurationError { 63 if (nextName != null) { 64 return true; 65 } 66 if (configs == null) { 67 try { 68 String fullName = prefix + service.getName(); 69 if (loader == null) 70 configs = ClassLoader.getSystemResources(fullName); 71 else 72 configs = loader.getResources(fullName); 73 } catch (IOException x) { 74 fail(service, ": " + x); 75 } 76 } 77 while ((pending == null) || !pending.hasNext()) { 78 if (!configs.hasMoreElements()) { 79 return false; 80 } 81 pending = parse(service, (URL)configs.nextElement(), returned); 82 } 83 nextName = (String)pending.next(); 84 return true; 85 } 86 87 public Object next() throws ServiceConfigurationError { 88 if (!hasNext()) { 89 throw new NoSuchElementException(); 90 } 91 String cn = nextName; 92 nextName = null; 93 try { 94 return Class.forName(cn, true, loader).newInstance(); 95 } catch (ClassNotFoundException x) { 96 fail(service, 97 "Provider " + cn + " not found"); 98 } catch (Exception x) { 99 fail(service, 100 "Provider " + cn + " could not be instantiated: " + x, 101 x); 102 } 103 return null; /* This cannot happen */ 104 } 105 106 public void remove() { 107 throw new UnsupportedOperationException(); 108 } 109 110 }
好了。经过各种进入,终于到达了目的地,上面这段代码就是加载数据库驱动的所在,请看LazyIterator里的从L57开始的这一段
实际上很简单,他就是去CLASSPATH里的library里找 META-INF/services/java.sql.Driver 这个文件,其中 java.sql.Driver 这个名字是通过上面的 service.getName()获得的。 数据库驱动的类里都会有 META-INF 这个文件夹,我们可以MySQL的connector/j数据库驱动加到环境变量里后自己尝试一下输出,代码如下
1 package test; 2 3 import java.io.IOException; 4 import java.net.URL; 5 import java.sql.Driver; 6 import java.util.Enumeration; 7 8 public class Test { 9 public static void main(String[] args) throws IOException { 10 Enumeration<URL> list = ClassLoader.getSystemResources("META-INF/services/" + Driver.class.getName()); 11 while (list.hasMoreElements()) { 12 System.out.println(list.nextElement()); 13 } 14 } 15 }
控制台会输出
jar:file:/usr/local/glassfish3/jdk7/jre/lib/resources.jar!/META-INF/services/java.sql.Driver
jar:file:/home/alexis/mysql-connector/mysql-connector-java-5.1.22-bin.jar!/META-INF/services/java.sql.Driver
看到了吗,这两个jar文件一个是jdk自带的,另一个是我们自己加到环境变量里的mysql驱动,然后我们再看看这两个java.sql.Driver里的东西,他们分别是
sun.jdbc.odbc.JdbcOdbcDriver
com.mysql.jdbc.Driver
自此,我们终于找到了我们需要加载的两个数据库驱动类的名称。然后再看LazyItarator里的next方法,注意到里面的forName了吧,这个方法就是加载类信息。顺便提一下,实际上forName方法里也是调用的ClassLoader的loadClass()方法来加载类信息的。
这里还有一步很关键的,就是加载类信息的时候发生了什么。我们看看 com.mysql.jdbc.Driver 的源码
1 public class Driver extends NonRegisteringDriver implements java.sql.Driver { 2 // ~ Static fields/initializers 3 // --------------------------------------------- 4 5 // 6 // Register ourselves with the DriverManager 7 // 8 static { 9 try { 10 java.sql.DriverManager.registerDriver(new Driver()); 11 } catch (SQLException E) { 12 throw new RuntimeException("Can't register driver!"); 13 } 14 } 15 16 // ~ Constructors 17 // ----------------------------------------------------------- 18 19 /** 20 * Construct a new driver and register it with DriverManager 21 * 22 * @throws SQLException 23 * if a database error occurs. 24 */ 25 public Driver() throws SQLException { 26 // Required for Class.forName().newInstance() 27 } 28 }
注意到这个static语句块了吧。就是这段代码,把自己注册到了DriverManager的driverlist里。
终于结束了,当所有驱动程序的Driver实例注册完毕,DriverManager就开始遍历这些注册好的驱动,对传入的数据库链接DSN调用这些驱动的connect方法,最后返回一个对应的数据库驱动类里的connect方法返回的java.sql.Connection实例,也就是我最开始那段测试代码里的conn。大家可以返回去看看DriverManager在initialize()结束后干了什么就明白
最后总结一下流程:
1. 调用 getConnection 方法
2. DriverManager 通过 SystemProerty jdbc.driver 获取数据库驱动类名
或者
通过ClassLoader.getSystemResources 去CLASSPATH里的类信息里查找 META-INF/services/java.sql.Driver 这个文件里查找获取数据库驱动名
3. 通过找的的driver名对他们进行类加载
4. Driver类在被加载的时候执行static语句块,将自己注册到DriverManager里去
5. 注册完毕后 DriverManager 调用这些驱动的connect方法,将合适的Connection 返回给客户端