事情还要从JDBC连接数据库开始说起:
1
//加载驱动( mysql的驱动很有意思)
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/database_name", "root", "root");
在com.mysql.cj.jdbc.Driver 这个类里面有这样一个静态代码块,在类加载的时候就会执行
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
手动的方式加载,最后可以吧驱动加载到DriverManager里面,最后被调用来获取连接;
4.0之前Class.forName()会使用调者的类加载器去加载ClassLoader.getClassLoader(caller)这个驱动
(也就是当前线程ApplicationClassLoader)
2
JDBC4.0之后采用的SPI的机制,让我们不需要书写Class.forName("com.mysql.cj.jdbc.Driver");这行代码同样可以产生相同的结果;
深入去看DriverManager.getConnection()就会发现最终会调用到ServiceLoader.load(Driver.class)
在这个方法里面有这么一行代码
ClassLoader cl = Thread.currentThread().getContextClassLoader();
获取上下文类加载器,并且将这个上下文类加载器保存在ServiceLoader实例中,并返回;
之后会执行ServiceLoader的一个迭代器(是他的一个内部类LazyClassPathLookupIterator)
loadedDrivers.iterator();
就是在这个迭代器里面实现的SPI机制,
他通过字符串拼接找到classpath的文件:META-INF/services/java.sql.Driver 这个文件
读取里面的权限定名(com.mysql.cj.jdbc.Driver);
通过Class.forName(url,true,classLoader)来加载Class
这个classLoader就是之前实例化ServiceLoader时保存的上下文来加载器;
然后得到了我们想要的Driver.class实例;
那么他哪里打破的双亲委派机制呢?
因为DriverManager是BootStracpClassLoader启动类加载器加载的,这个时候我们的mysql驱动实例
可没有在JAVA_HOME/lib/目录下,而是在classpath目录下(需要的是Application类加载器去加载)
就不能被启动类加载器加载,但是我们又需要mysql驱动(Driver实例)
去获取mysql连接,就得向下委派了(父加载器 需要子加载器去加载,而双亲委派是子加载器委派父加载器加载)
所以打破双亲委派的关键在于获取了getContextClassLoader()上下文类加载器;
有错Call 我