⼀句话总结:
1 ⾃动加载:DriverManager的静态代码块执⾏的时刻,使⽤该时刻当前线程类加载器加载java.sql.Driver⽂件,并⽤当前线程类加载器加载及、实例化、registerDriver
2 没有⾃动加载的,⼿动forName,传⼊⼀个类加载器及是否初始化;或loadClass实例化()
3 getConncetion时,校验调⽤getConnection的类所在类加载器与driver实现所在类加载器(校验存放的driver是否属于调⽤者的Classloader), 这个连接通过伪造⼀个中间⼈骗过校验
SPI概述
SPI全称为(Service Provider Interface) ,是JDK内置的⼀种服务提供发现机制;主要被框架的开发⼈员使⽤,⽐如java.sql.Driver接⼝,数据库⼚商实现此接⼝即可,当然要想让系统知道具体实现类的存在,还需要使⽤固定的存放规则,需要在classpath下的META-INF/services/⽬录⾥创建⼀个以服务接⼝命名的⽂件,这个⽂件⾥的内容就是这个接⼝的具体的实现类;下⾯以JDBC为实例来进⾏具体的分析。
JDBC驱动
1.准备驱动包
分别准备了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 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 ServiceLoader /* 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 Class Enumeration private LazyIterator(Class 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\ } } 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,然后通过类加载器()去获取所有类路径(当前类加载)下java.sql.Driver⽂件,获取之后存放在configs中,⾥⾯的每个元素对应⼀个⽂件,每个⽂件中可能会存在多个驱动类,所以使⽤pending⽤来存放每个⽂件中的驱动信息,获取驱动信息之后在nextService中使⽤Class.forName加载类信息,并且指定不进⾏初始化;同时在下⾯使⽤newInstance 触发各驱动的静态代码块()对驱动类进⾏了实例化操作;每个驱动类中都提供了⼀个静态注册代码块,⽐如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\ } 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)) {(中⽤⼀个代理中间⼈骗过了) 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,如果中途有失败的连接并不影响尝试新的驱动连接,遍历完之后还是⽆法获取连接,则抛出异常; 因篇幅问题不能全部显示,请点此查看更多更全内容 { service; ClassLoader loader; service, ClassLoader loader) { this.service = service; this.loader = loader; }