https://segmentfault.com/a/1190000017517197?utm_source=tag-newest
一句話總結:
1 自動加載:DriverManager的靜態代碼塊執行的時刻,使用該時刻當前線程類加載器加載java.sql.Driver文件,並用當前線程類加載器加載及、實例化、registerDriver
2 沒有自動加載的,手動forName,傳入一個類加載器及是否初始化;或loadClass➕實例化(load/find class 與 forname 在static代碼塊加載的不同 (二)非系統類jdbc)
3 getConncetion時,校驗調用getConnection的類所在類加載器與driver實現所在類加載器(校驗存放的driver是否屬於調用者的Classloader),JDBC注冊原理與自定義類加載器解決com.cloudera.hive.jdbc41.HS2Driver的加載【重點】 這個連接通過偽造一個中間人騙過校驗
SPI概述
SPI全稱為(Service Provider Interface) ,是JDK內置的一種服務提供發現機制;主要被框架的開發人員使用,比如java.sql.Driver接口,數據庫廠商實現此接口即可,當然要想讓系統知道具體實現類的存在,還需要使用固定的存放規則,需要在classpath下的META-INF/services/目錄里創建一個以服務接口命名的文件,這個文件里的內容就是這個接口的具體的實現類;下面以JDBC為實例來進行具體的分析。
JDBC驅動
1.准備驅動包
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.2</version> </dependency> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>mssql-jdbc</artifactId> <version>7.0.0.jre8</version> </dependency>
分別准備了mysql,postgresql和sqlserver,可以打開jar,發現每個jar包的META-INF/services/都存在一個java.sql.Driver文件,文件里面存在一個或多個類名,比如mysql:
com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver
提供的每個驅動類占據一行,解析的時候會按行讀取,具體使用哪個會根據url來決定;
2.簡單實例
String url = "jdbc:mysql://localhost:3306/db3"; String username = "root"; String password = "root"; String sql = "update travelrecord set name=\'bbb\' where id=1"; Connection con = DriverManager.getConnection(url, username, password);
類路徑下存在多個驅動包,具體在使用DriverManager.getConnection應該使用哪個驅動類會解析url來識別,不同的數據庫有不同的url前綴;
3.驅動類加載分析
具體META-INF/services/下的驅動類是什么時候加載的,DriverManager有一個靜態代碼塊:
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() { String drivers; try { drivers = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run() { return System.getProperty("jdbc.drivers"); } }); } catch (Exception ex) { drivers = null; } // If the driver is packaged as a Service Provider, load it. // Get all the drivers through the classloader // exposed as a java.sql.Driver.class service. // ServiceLoader.load() replaces the sun.misc.Providers() AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);【會遍歷DriverManage靜態代碼塊執行時,當前線程類加載器下找】 Iterator<Driver> driversIterator = loadedDrivers.iterator(); /* Load these drivers, so that they can be instantiated. * It may be the case that the driver class may not be there * i.e. there may be a packaged driver with the service class * as implementation of java.sql.Driver but the actual class * may be missing. In that case a java.util.ServiceConfigurationError * will be thrown at runtime by the VM trying to locate * and load the service. * * Adding a try catch block to catch those runtime errors * if driver not available in classpath but it's * packaged as service and that service is there in classpath. */ try{ while(driversIterator.hasNext()) { driversIterator.next();【重要,在這一句forname和實例化】 } } catch(Throwable t) { // Do nothing } return null; } }); println("DriverManager.initialize: jdbc.drivers = " + drivers); 【第二種方式】 if (drivers == null || drivers.equals("")) { return; } String[] driversList = drivers.split(":"); println("number of Drivers:" + driversList.length); for (String aDriver : driversList) { try { println("DriverManager.Initialize: loading " + aDriver); Class.forName(aDriver, true, ClassLoader.getSystemClassLoader()); } catch (Exception ex) { println("DriverManager.Initialize: load failed: " + ex); } } }
在加載DriverManager類的時候會執行loadInitialDrivers方法,方法內通過了兩種加載驅動類的方式,分別是:使用系統變量方式和ServiceLoader加載方式;系統變量方式其實就是在變量jdbc.drivers中配置好驅動類,然后使用Class.forName進行加載;下面重點看一下ServiceLoader方式,此處調用了load方法但是並沒有真正去加載驅動類,而是返回了一個LazyIterator,后面的代碼就是循環變量迭代器:
private static final String PREFIX = "META-INF/services/"; private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null; private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); service:java.sql.Driver if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName);【重要,遍歷META-INF/servises/java.sql.Driver資源文件】 } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { c = Class.forName(cn, false, loader); 【重要,使用傳入的當前線程類加載器加載】 } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen } ...... }
類中指定了一個靜態常量PREFIX = “META-INF/services/”,然后和java.sql.Driver拼接組成了fullName,然后通過類加載器(classloader getresource jar包資源沖突情況,父親為大,兄弟之間,誰先加載誰牛逼(二))去獲取所有類路徑(當前類加載)下java.sql.Driver文件,獲取之后存放在configs中,里面的每個元素對應一個文件,每個文件中可能會存在多個驅動類,所以使用pending用來存放每個文件中的驅動信息,獲取驅動信息之后在nextService中使用Class.forName加載類信息,並且指定不進行初始化;同時在下面使用newInstance 觸發各驅動的靜態代碼塊(load/find class 與 forname 在static代碼塊加載的不同)對驅動類進行了實例化操作;每個驅動類中都提供了一個靜態注冊代碼塊,比如mysql:
static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
這里又實例化了一個驅動類,同時注冊到DriverManager;接下來就是調用DriverManager的getConnection方法,代碼如下:
private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { /* * When callerCl is null, we should check the application's * (which is invoking this class indirectly) * classloader, so that the JDBC driver class outside rt.jar * can be loaded from here. */ ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;【調用方的類所在類加載器】 synchronized(DriverManager.class) { // synchronize loading of the correct classloader. if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } if(url == null) { throw new SQLException("The url cannot be null", "08001"); } println("DriverManager.getConnection(\"" + url + "\")"); // Walk through the loaded registeredDrivers attempting to make a connection. // Remember the first exception that gets raised so we can reraise it. SQLException reason = null; for(DriverInfo aDriver : registeredDrivers) { // If the caller does not have permission to load the driver then // skip it. if(isDriverAllowed(aDriver.driver, callerCL)) {(JDBC注冊原理與自定義類加載器解決com.cloudera.hive.jdbc41.HS2Driver的加載【重點】中用一個代理中間人騙過了) 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()); } } // if we got here nobody could connect. if (reason != null) { println("getConnection failed: " + reason); throw reason; } println("getConnection: no suitable driver found for "+ url); throw new SQLException("No suitable driver found for "+ url, "08001"); }
此方法主要是遍歷之前注冊的DriverInfo,拿着url信息去每個驅動類中建立連接,當然每個驅動類中都會進行url匹配校驗,成功之后返回Connection,如果中途有失敗的連接並不影響嘗試新的驅動連接,遍歷完之后還是無法獲取連接,則拋出異常;