参见如下简单的程序
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 * made10 * @param password the user's password11 * @return a connection to the URL 12 * @exception SQLException if a database access error occurs13 */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 drivers30 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.Driver31 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 there15 * i.e. there may be a packaged driver with the service class16 * as implementation of java.sql.Driver but the actual class17 * may be missing. In that case a sun.misc.ServiceConfigurationError18 * 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 errors22 * 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 while30 } catch(Throwable t) {31 // Do nothing32 }33 return null;34 } //end run35 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.Driverjar: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.JdbcOdbcDrivercom.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 // ~ Constructors17 // -----------------------------------------------------------18 19 /**20 * Construct a new driver and register it with DriverManager21 * 22 * @throws SQLException23 * 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 返回给客户端