JDBCドライバロードプロセス

51132 ワード

以下の簡単なプログラムを参照
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     }

ここにはいくつかの重要な点があります.L 25の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を例にとると、L 27を見て、この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

L 11の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のL 57から始まるこの部分を見てください.
実は簡単です.彼はCLASSSPATHのlibraryに行ってMETA-INF/services/javaを探しています.sql.Driverこのファイル、java.sql.Driverという名前は上のサービスを通じています.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

見ましたか?この2つのjarファイルは1つはjdkが持参したもので、もう1つは私たちが環境変数に追加したmysqlドライバで、それからこの2つのjavaを見てみましょう.sql.Driverの中のものはそれぞれ
sun.jdbc.odbc.JdbcOdbcDriver

com.mysql.jdbc.Driver

これにより、ロードする必要がある2つのデータベース・ドライバ・クラスの名前が見つかりました.それからLazyItaratorのnextメソッドを見て、中のforNameに気づいたでしょう.このメソッドはクラス情報をロードすることです.ちなみに、実際にforNameメソッドでもクラス情報をロードするためにClassLoaderのloadClass()メソッドが呼び出されています.
ここでもう一つ重要なのは、クラス情報をロードしたときに何が起こったのかということです.見てみましょう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インスタンスの登録が完了すると、Driver Managerはこれらの登録済みドライバを巡回し始め、転送されたデータベースリンクDSNに対してこれらのドライバのconnectメソッドを呼び出し、最後に対応するデータベースドライバクラスのconnectメソッドが返すjavaを返す.sql.Connectionインスタンス、つまり私が最初にテストコードに入れたconnです.DriverManagerがinitialize()が終わった後に何をしたか見てみましょう
 
最後にプロセスをまとめます.
1.getConnectionメソッドの呼び出し
2.DriverManagerはSystemProerty jdbcを通過する.driverデータベースドライバクラス名の取得
または
ClassLoaderを通すgetSystemResources CLASSSPATHのクラス情報でMETA-INF/services/javaを検索します.sql.Driverこのファイルでデータベースドライバ名を検索
3.探したdriver名でクラスをロードする
4.Driverクラスはロードされたときにstatic文ブロックを実行し、自分をDriverManagerに登録する
5.登録完了後、DriverManagerはこれらのドライバの接続方法を呼び出し、適切な接続をクライアントに返す