探究MySQL8.0驱动的加载
大家在连接mysql的时候,启动项目,会警告你推荐使用com.mysql.cj.jdbc.Driver 而不是com.mysql.jdbc.Driver
那么这两者到底有什么区别呢
本质区别:
com.mysql.jdbc.Driver 是 mysql-connector-java 5中的,需要手动加载驱动
com.mysql.cj.jdbc.Driver 是 mysql-connector-java 6以及以上中的,不需要手动加载驱动
驱动的加载
在低版本驱动中,我们通常使用class.forName("com.mysql.jdbc.Driver")
来手动加载驱动
但在高版本中这段代码却被省略了,例如下面这段代码,并没有手动加载驱动,却依旧可以正常执行,那么MySQL驱动是如何被加载的呢?
public class Test {
public static void main(String[] args) throws Exception{
// 数据库地址 + 要使用的数据库名
String url = "jdbc:mysql://localhost:3306/" + "test";
// 你要登陆的数据库用户,一般为 root
String user = "root";
// 你要登录用户的密码
String password = "12345678";
DriverManager.setLogWriter(new PrintWriter(System.out));
Connection con = DriverManager.getConnection(url, user, password);
// 输出连接
System.out.println(con);
// 关闭连接
con.close();
}
}
点进去DriverManager.getConnection
,并没有发现有用的信息
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
我们顺藤摸瓜继续点(getConnection(url, info, Reflection.getCallerClass()));
,发现如下代码
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;
if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) {
callerCL = Thread.currentThread().getContextClassLoader();
}
if (url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
//--------------------------------------------------------------------
//其他的都和驱动的自动加载没什么关系,只有这个才是重点
ensureDriversInitialized();
//--------------------------------------------------------------------
// 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.driver.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");
}
ensureDriversInitialized();
翻译过来就是 确保驱动程序已初始化 我们点进去看看
private static void ensureDriversInitialized() {
if (driversInitialized) {
return;
}
synchronized (lockForInitDrivers) {
if (driversInitialized) {
return;
}
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty(JDBC_DRIVERS_PROPERTY);
}
});
} 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);
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();
}
} catch (Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers != null && !drivers.isEmpty()) {
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);
}
}
}
//注意这里(driversInitialized=驱动程序已初始化),这是DriverManager的一个私有字段
driversInitialized = true;
println("JDBC DriverManager initialized");
}
}
这里明确把driversInitialized
标为true
说明驱动在它之前就加载好了,那么我们往上看,这里有个同步代码块
synchronized (lockForInitDrivers) {
if (driversInitialized) {
return;
}
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty(JDBC_DRIVERS_PROPERTY);
}
});
} 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.
//公开为 java.sql.Driver.class 服务。
// ServiceLoader.load() replaces the sun.misc.Providers()
//ServiceLoader.load() 替换了 sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
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.
*加载这些驱动程序,以便可以实例化它们。可能不存在驱动程序类的情况,即可能有一个打包的驱动程序,其中服务类作为 java.sql.Driver 的实现,但实际类可能丢失。在这种情况下,尝试查找和加载服务的 VM 将在运行时抛出 java.util.ServiceConfigurationError。
* 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 catch 块以捕获这些运行时错误。
*/
try {
while (driversIterator.hasNext()) {
driversIterator.next();
}
} catch (Throwable t) {
// Do nothing
}
return null;
}
});
通过断点调试我们发现,在这里 while (driversIterator.hasNext())
经历过一次循环后registeredDrivers
(DriverManager的私有变量,用于存储所有注册的驱动程序)的size变为 1 ,这说明driversIterator
在进行便利的时候加载Driver
这个类
往上找,我们在注释上面有这两行代码,注释也表明就是在这里加载驱动的,我们点进去了解一下
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
在这里同个静态方法创建ServiceLoader
了这个类,继续往下追踪
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
对ServiceLoader
的一些初始化,没有发现什么有用的信息
private ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl) {
Objects.requireNonNull(svc);
if (VM.isBooted()) {
checkCaller(caller, svc);
if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
}
} else {
// if we get here then it means that ServiceLoader is being used
//如果我们到达这里,则意味着正在使用 ServiceLoader
// before the VM initialization has completed. At this point then
//在 VM 初始化完成之前。在这一点上
// only code in the java.base should be executing.
//只有 java.base 中的代码应该执行。
Module callerModule = caller.getModule();
Module base = Object.class.getModule();
Module svcModule = svc.getModule();
if (callerModule != base || svcModule != base) {
fail(svc, "not accessible to " + callerModule + " during VM init");
}
// restricted to boot loader during startup
cl = null;
}
this.service = svc;
this.serviceName = svc.getName();
this.layer = null;
this.loader = cl;
this.acc = (System.getSecurityManager() != null)
? AccessController.getContext()
: null;
}
我们再看这一行代码,点进去loadedDrivers.iterator()
了解一下
Iterator<Driver> driversIterator = loadedDrivers.iterator();
这里有一个迭代器内部类,返回了一个匿名类
public Iterator<S> iterator() {
// create lookup iterator if needed 如果需要,创建查找迭代器
if (lookupIterator1 == null) {
lookupIterator1 = newLookupIterator();
}
return new Iterator<S>() {
// record reload count 记录重新加载计数
final int expectedReloadCount = ServiceLoader.this.reloadCount;
// index into the cached providers list 索引到缓存的提供程序列表中
int index;
/**
* Throws ConcurrentModificationException if the list of cached
* providers has been cleared by reload.
*如果缓存的提供程序列表已被重新加载清除,则引发 ConcurrentModificationException。
*/
private void checkReloadCount() {
if (ServiceLoader.this.reloadCount != expectedReloadCount)
throw new ConcurrentModificationException();
}
@Override
public boolean hasNext() {
checkReloadCount();
if (index < instantiatedProviders.size())
return true;
return lookupIterator1.hasNext();
}
@Override
public S next() {
checkReloadCount();
S next;
if (index < instantiatedProviders.size()) {
next = instantiatedProviders.get(index);
} else {
next = lookupIterator1.next().get();
instantiatedProviders.add(next);
}
index++;
return next;
}
};
}
注意这里创建了一个查找迭代器,会被下面返回的匿名迭代器使用,点进去newLookupIterator
看看
// create lookup iterator if needed 如果需要,创建查找迭代器
if (lookupIterator1 == null) {
lookupIterator1 = newLookupIterator();
}
断点调试走了else这条分支,发现这里创建了两个查找迭代器,并返回一个迭代器匿名类,而且这两个查找迭代器由于作用域的关系还成了这个匿名类的私有属性(类似闭包),我们分别查看这两个查找的迭代器
private Iterator<Provider<S>> newLookupIterator() {
assert layer == null || loader == null;
if (layer != null) {
return new LayerLookupIterator<>();
} else {
//模块服务查找迭代器
Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
//惰性类路径查找迭代器
Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
return new Iterator<Provider<S>>() {
@Override
public boolean hasNext() {
return (first.hasNext() || second.hasNext());
}
@Override
public Provider<S> next() {
if (first.hasNext()) {
return first.next();
} else if (second.hasNext()) {
return second.next();
} else {
throw new NoSuchElementException();
}
}
};
}
}
在LazyClassPathLookupIterator
中发现了端倪static final String PREFIX = "META-INF/services/"
,查看 mysql jar包路径下的META-INF/services/
文件
private final class LazyClassPathLookupIterator<T>
implements Iterator<Provider<T>>
{
static final String PREFIX = "META-INF/services/";
Set<String> providerNames = new HashSet<>(); // to avoid duplicates 避免重复
Enumeration<URL> configs;
Iterator<String> pending;
Provider<T> nextProvider;
ServiceConfigurationError nextError;
LazyClassPathLookupIterator() { }
/**
* Parse a single line from the given configuration file, adding the
* name on the line to set of names if not already seen.
从给定的配置文件中解析一行,如果尚未看到,则将该行的名称添加到一组名称中。
*/
private int parseLine(URL u, BufferedReader r, int lc, Set<String> names)
throws IOException
{
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
int start = Character.charCount(cp);
for (int i = start; i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
if (providerNames.add(ln)) {
names.add(ln);
}
}
return lc + 1;
}
/**
* Parse the content of the given URL as a provider-configuration file.
将给定 URL 的内容解析为提供程序配置文件
*/
private Iterator<String> parse(URL u) {
Set<String> names = new LinkedHashSet<>(); // preserve insertion order 保留插入顺序
try {
URLConnection uc = u.openConnection();
uc.setUseCaches(false);
try (InputStream in = uc.getInputStream();
BufferedReader r
= new BufferedReader(new InputStreamReader(in, UTF_8.INSTANCE)))
{
int lc = 1;
while ((lc = parseLine(u, r, lc, names)) >= 0);
}
} catch (IOException x) {
fail(service, "Error accessing configuration file", x);
}
return names.iterator();
}
/**
* Loads and returns the next provider class. 加载并返回下一个提供程序类
*/
private Class<?> nextProviderClass() {
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null) {
configs = ClassLoader.getSystemResources(fullName);
} else if (loader == ClassLoaders.platformClassLoader()) {
// The platform classloader doesn't have a class path,
// but the boot loader might.
//平台类加载器没有类路径,但引导加载器可能
if (BootLoader.hasClassPath()) {
configs = BootLoader.findResources(fullName);
} else {
configs = Collections.emptyEnumeration();
}
} else {
configs = loader.getResources(fullName);
}
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return null;
}
pending = parse(configs.nextElement());
}
String cn = pending.next();
try {
return Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
return null;
}
}
@SuppressWarnings("unchecked")
private boolean hasNextService() {
while (nextProvider == null && nextError == null) {
try {
Class<?> clazz = nextProviderClass();
if (clazz == null)
return false;
if (clazz.getModule().isNamed()) {
// ignore class if in named module 如果在命名模块中,则忽略类
continue;
}
if (service.isAssignableFrom(clazz)) {
Class<? extends S> type = (Class<? extends S>) clazz;
Constructor<? extends S> ctor
= (Constructor<? extends S>)getConstructor(clazz);
ProviderImpl<S> p = new ProviderImpl<S>(service, type, ctor, acc);
nextProvider = (ProviderImpl<T>) p;
} else {
fail(service, clazz.getName() + " not a subtype");
}
} catch (ServiceConfigurationError e) {
nextError = e;
}
}
return true;
}
private Provider<T> nextService() {
if (!hasNextService())
throw new NoSuchElementException();
Provider<T> provider = nextProvider;
if (provider != null) {
nextProvider = null;
return provider;
} else {
ServiceConfigurationError e = nextError;
assert e != null;
nextError = null;
throw e;
}
}
@SuppressWarnings("removal")
@Override
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
@SuppressWarnings("removal")
@Override
public Provider<T> next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<Provider<T>> action = new PrivilegedAction<>() {
public Provider<T> run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
}
在此路径下确实找到了com.mysql.cj.jdbc.Driver
的全限定名
断点调试,也确实发现它拿到了驱动类的全限定名,剩下的也就是加载驱动了,不在探究
总结
DriverManager
通过扫描每个包下的META-INF/services/
的对应文件获取驱动类的去限定名,然后再去加载它
有兴趣可以去了解一下 SPI(Service Provider Interface,服务提供接口) (对你可能有帮助)
推荐文章:【Java】SPI介绍及实例分析_spi 实例讲解-CSDN博客
标签:return,service,探究,MySQL8.0,new,null,class,加载 From: https://www.cnblogs.com/starychen/p/18119834