场景
maven配置
mysql
mysql-connector-java
5.1.41
java代码
public static void main(String[] args)
{
Enumeration drivers = DriverManager.getDrivers();
Driver driver;
while (drivers.hasMoreElements())
{
driver = drivers.nextElement();
System.out.println(driver.getClass() + "------" + driver.getClass().getClassLoader());
}
System.out.println(DriverManager.class.getClassLoader());
}
输出结果如下:
class com.mysql.jdbc.Driver------sun.misc.Launcher$AppClassLoader@2a139a55
class com.mysql.fabric.jdbc.FabricMySQLDriver------sun.misc.Launcher$AppClassLoader@2a139a55
null
问题
JDBC SPI是如何初始化的?
1. DriverManager类
首先,我们看到main方法是调用了DriverManager方法拿到mysql的驱动类的。
public class DriverManager {
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
}
DriverManager类在加载时,会调用loadInitialDrivers()方法。
private static void loadInitialDrivers() {
...
// 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() {
public Void run() {
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
...
loadInitialDrivers()方法会调用ServiceLoader.load(Driver.class)。这个方法是让ServiceLoader去加载Driver.class的所有实现类。
2. ServiceLoader类
public final class ServiceLoader
implements Iterable
{
private static final String PREFIX = "META-INF/services/";
/**
* Clear this loader's provider cache so that all providers will be
* reloaded.
*
* After invoking this method, subsequent invocations of the {@link
* #iterator() iterator} method will lazily look up and instantiate
* providers from scratch, just as is done by a newly-created loader.
*
*
This method is intended for use in situations in which new providers
* can be installed into a running Java virtual machine.
*/
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
private ServiceLoader(Class svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
那ServiceLoader是怎么知道要去加载哪些类呢?
这里的秘密就在于Driver.class。ServiceLoader会去根据要加载的接口类读取特定目录下的配置文件。这里为 "META-INF/services/" + Driver.class,即"META-INF/services/java.sql.Driver"文件。
例如mysql-connector-java-5.1.41.jar!/META-INF/services/java.sql.Driver的内容为:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
就是告诉ServiceLoader,我这个包实现了这两个Driver,你去加载一下吧。
3. LazyIterator
ServiceLoader初始化以后,会持有一个LazyIterator迭代器。
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
DriverManager拿到ServiceLoader实例以后,会对其进行迭代以获得Driver的实现类。
private class LazyIterator
implements Iterator
{
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");
}
...
核心代码是Class.forName(cn, false, loader),它会动态加载Driver的实现类。
4. Driver类
这里的Driver类指的是mysql的实现类,完整包名为com.mysql.jdbc.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
可以看到,最后Driver通过调用DriverManager.registerDriver(new Driver()),将自身实例注册回了DriverManager。
这里同时解释了一个现象,在第三步,为什么用Class.forName(),而不用ClassLoader.loadClass()。因为ClassLoader.loadClass()不会初始化静态块。用了ClassLoader.loadClass(),就无法将自身实例注入到DriverManager中去了。
关键点总结:
- DriverManager
- Driver.class
- ServiceLoader
- META-INF/services/java.sql.Driver
参考资料:jdbc 类加载器,与 spi 服务机制