jdbc不用显示地写Class.forName(driver);
在这之前,有一个问题就是,在java程序中,是可以不用显式地加载的,但是在tomcat部署web应用时,必须要显式加载,我在tomcat的lib目录中加入了mysql的jar包也不行,而且debug发现程序也找到了对应的文件并加载了驱动,但还是抛出了异常,表示找不到合适的驱动来加载,希望有大佬能解释一下。
/*getConnection为DriverManager的静态方法,在执行方法之前
DriverManager会先进行初始化
*/
DriverManager.getConnection(url,username,password);
/*
初始化时会执行静态代码块中的代码
*/
static {
//此方法会加载初始化的驱动
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
//从系统设置中获取到drivers的字符串,为驱动类的全限定名
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
//AccessController.doPrivileged 访问控制,获得特权,不用检查
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//由当前线程获取到上下文类加载器
//通过ServiceLoad从META-INF.services目录下加载类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
//这一步放到后面讲
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
//什么都不做
}
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中
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!");
}
}
}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
//注册驱动
if(driver != null) {
//addIfAbsent
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
...
try{
//这一步放到后面讲
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
//什么都不做
}
...
在获取到迭代的之后,进入到一个while循环,调用hasNext()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1KZDVRvO-1660806023847)(C:\Users\郑蒙恩\AppData\Roaming\Typora\typora-user-images\image-20220818140503627.png)]
//进入ServiceLoader中的懒加载器LazyIterator类中的方法
public boolean hasNext() {
if (knownProviders.hasNext())//false
return true;
return lookupIterator.hasNext();
}
//进入到lookuoIterator.hasNext() ,
public boolean hasNext() {
if (acc == null) { //true
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
//进入hasNextService()
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
/*
private static final String PREFIX = "META-INF/services/";
service就是java.sql.Driver
*/
String fullName = PREFIX + service.getName();
if (loader == null)//false
//如果loader为空,类加载器会去加载META-INF/services/的java.sql.Driver文件到configs
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);//configs是Enumeration<URL>类
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
//如果configs没有更多的元素,返回false
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
//进入到configs.hasMoreElements()
//public class CompoundEnumeration<E> implements Enumeration<E> 下的next方法
private boolean next() {
//根据enums的长度遍历,又下图可知enums为2
while(this.index < this.enums.length) {
//如果index位置不为空,且该位置的复合枚举有更多的元素,返回true
/*
index[0]没有更多的元素
*/
if (this.enums[this.index] != null && this.enums[this.index].hasMoreElements()) {
return true;
}
++this.index;
}
return false;
}
//进入index[1]的hasMoreElements()
//进入到URLClassLoader的next()方法
private boolean next() {
if (url != null) {
return true;
}
do {
URL u = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
/*
final Enumeration<URL> e = ucp.findResources(name, true);
ucp是URLClassLoader对象
*/
//e是一个Enumeration<URL>对象,e具体的内容看下图
//进入到e.hasMoreElements
if (!e.hasMoreElements())
return null;
return e.nextElement();
}
}, acc);
if (u == null)
break;
url = ucp.checkURL(u);
} while (url == null);
return url != null;
}
//
private boolean next() {
if (this.url != null) {
return true;
} else {
do {
URLClassPath.Loader var1x;
//遍历所有的URLClassPath,直到遍历完
if ((var1x = URLClassPath.this.getNextLoader(this.cache, this.index++)) == null) {
return false;
}
//将找到的资源赋值给url
this.url = var1x.findResource(var1, var2);
} while(this.url == null);//如果加载器都没有找到这个资源,就循环,直到找到。
return true;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-izvgLfJR-1660806023848)(C:\Users\郑蒙恩\AppData\Roaming\Typora\typora-user-images\image-20220818141749554.png)]
index[0]的hasMoreElements()为false
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MdWPme0Y-1660806023849)(C:\Users\郑蒙恩\AppData\Roaming\Typora\typora-user-images\image-20220818142301045.png)]
URLClassPath
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yZtH66FR-1660806023850)(C:\Users\郑蒙恩\AppData\Roaming\Typora\typora-user-images\image-20220818142730609.png)]
30个类加载器对应加载的类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nL7PczRG-1660806023852)(C:\Users\郑蒙恩\AppData\Roaming\Typora\typora-user-images\image-20220818143507636.png)]
循环到了mysql的jar,找到了对应的META-INF/service/java.sql.Driver,退出循环,返回true
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j5jheNjt-1660806023854)(C:\Users\郑蒙恩\AppData\Roaming\Typora\typora-user-images\image-20220818143825940.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JZH5dQgN-1660806023855)(C:\Users\郑蒙恩\AppData\Roaming\Typora\typora-user-images\image-20220818143843772.png)]
然后将结果返回个URL的对象u
URL u = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
if (!e.hasMoreElements())
return null;
return e.nextElement();
}
}, acc);
如果u不为空,就检查这个URL并赋值给url
if (u == null)
break;//如果u为空,则退出while循环,返回false
url = ucp.checkURL(u);
回到开始进入到configs.hasMoreElements()的if判断的语句
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {//false
return false;
}
//解析获取到的元素,并赋值给pending
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
pending是一个迭代器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6QElC9VP-1660806023856)(C:\Users\郑蒙恩\AppData\Roaming\Typora\typora-user-images\image-20220818144804521.png)]
调用pending的next(),得到com.sql.cj.jdbc.Driver并赋值给nextName
然后返回true
进入driversIterator.next()
while(driversIterator.hasNext()) {
driversIterator.next();
}
接着debug进入,到达ServiceLoader的懒加载类中的nextService方法
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;//拿到之前赋值过的nextName
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);//加载这个类,到这里就知道为什么不用自己加载驱动了
}
...
}
以上的过程,只是大概的流程,请debug跟一遍就清楚了。
nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;//拿到之前赋值过的nextName
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);//加载这个类,到这里就知道为什么不用自己加载驱动了
}
…
}
以上的过程,只是大概的流程,请debug跟一遍就清楚了。