SPI
JDBC连接数据库时为什么要Class.forName()问题
-
在使用Class.forName(com.mysql.jdbc.Driver);的时候会加载这个Driver类,这个Driver类中有静态代码块调用DriverManager.registerDriver(new Driver());语句注册驱动,静态代码块在加载类的时候就会执行,因此利用反射加载这个类就可以完成注册。
而DriverManager类里面有属性private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>(),这个集合保存了的是DriverInfo类型,这个类型包括注册的驱动和一个对应的data信息,这个data信息直接注册其实为null::
//源码
-
//源码 public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException { //这里用的是java里面的Driver接口接收jar包里面的Driver对象,因为它实现了这个接口 registerDriver(driver, null);//直接是null!!! } public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException { /* Register the driver if it has not already been added to our list */ if(driver != null) { registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); } else { // This is for compatibility with the original DriverManager throw new NullPointerException(); } println("registerDriver: " + driver); } ———————————————— 版权声明:本文为CSDN博主「开局一打酒。」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/w55935/article/details/123315870
然后在使用DriverManager.getConnection()得到连接的时候底层是遍历registeredDrivers集合得到一个DriverInfo对象aDriver,然后用aDriver对象调用这个对象里面接收驱动的属性driver,这个driver就是我们new出来传入的,再用这个driver调用connect方法:
-
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()); //下面这句话就是得到这个我们传入的驱动再调connect!!!!!!!!!!!!!!!! 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()); } } ———————————————— 版权声明:本文为CSDN博主「开局一打酒。」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/w55935/article/details/123315870
由上可知,我们虽然不推荐用直接new Driver()得到Driver对象再调用connect方法得到连接,但是用DriverManager得到连接的时候底层仍然是在Class.forName()new一个Driver对象注册进去,然后找出这个对象用它调用connect方法来得到连接。因此使用Class.forName()注册驱动必不可少,不然DriverManager里面的驱动集合里找不到它,无法进行连接。
-
JDBC为什么要破坏双亲委派模型#
问题背景
在JDBC 4.0之后实际上我们不需要再调用Class.forName来加载驱动程序了,我们只需要把驱动的jar包放到工程的类加载路径里,那么驱动就会被自动加载。
这个自动加载采用的技术叫做SPI,数据库驱动厂商也都做了更新。可以看一下jar包里面的META-INF/services目录,里面有一个java.sql.Driver的文件,文件里面包含了驱动的全路径名。
使用上,我们只需要通过下面一句就可以创建数据库的连接:
Copy
Connection con =
DriverManager.getConnection(url , username , password ) ;
问题解答
因为类加载器受到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,这时候就需要委托子类加载器去加载class文件。
JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包。DriverManager 类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,**根据类加载机制,当被装载的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。**也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。我们知道,BootStrap类加载器默认只负责加载 $JAVA_HOME中jre/lib/rt.jar 里所有的class,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。
查看DriverManager类的源码,看到在使用DriverManager的时候会触发其静态代码块,调用 loadInitialDrivers() 方法,并调用ServiceLoader.load(Driver.class) 加载所有在META-INF/services/java.sql.Driver 文件里边的类到JVM内存,完成驱动的自动加载。
Copy
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
}
Copy
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
这个子类加载器是通过 Thread.currentThread().getContextClassLoader() 得到的线程上下文加载器。
那么这个上下文类加载器又是什么呢?
Copy
public Launcher() {
...
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
...
}
可以看到,在 sun.misc.Launcher 初始化的时候,会获取AppClassLoader,然后将其设置为上下文类加载器,所以线程上下文类加载器默认情况下就是系统加载器。
注: 原文链接: