点击上方 IT牧场 ,选择 置顶或者星标技术干货每日送达!
前言
最近在看类加载器相关的知识发现Class.forName(“XXX”)和ClassLoader.load(“xxx”)在类加载流程里面是有比较大的区别。class.forName(“XXXX”)会执行加载-链接-初始化,而ClassLoader.load只会执行加载。
如果我们写Test测试可以发现,class.forName方式确实是可以走到初始化环节,而classLoder.load方式并不会。基于此想到基于原生jdbc的方式去操作db的步骤(本文基于jdk8 、mysql驱动包mysql-connector-java:5.1.34)去验证。
验证过程
jdbc操作db的方式 第一步是需要注册数据库驱动的。实例如下
public class Test1 {
public static void main(String[] args) throws Exception {
Class.forName(“com.mysql.jdbc.Driver”)
String url = "jdbc:mysql:/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=true&verifyServerCertificate=false&allowMultiQueries=true";
String user = "xx";
String password = "xx.db.2019!";
Connection connection = DriverManager.getConnection(url, user, password);
PreparedStatement preparedStatement = connection.prepareStatement("SELECT 1 FROM DUAL ");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
String string = resultSet.getString(1);
System.out.println(string);
}
connection.close();
}
}
Class.forName(“com.mysql.jdbc.Driver”) 会执行静态代码块并注册mysql的数据库驱动
此时程序运行会如期返回结果。
再将第三行换成如下代码
Thread.currentThread().getContextClassLoader().loadClass("com.mysql.jdbc.Driver");
发现结果依然能成功返回~
WTF ??
两次验证结果证明类加载时机,跟之前所说不太一致。这就有点懵了,按理说第二次验证应该提示错误才对,因为没有显式注册驱动。于是再次验证一个场景~
再次验证
这里做一个测试,如果将测试类中的注册驱动代码删掉,测试代码还能会继续执行吗?
public class Test1 {
public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://xx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=true&verifyServerCertificate=false&allowMultiQueries=true";
String user = "xx";
String password = "xx";
Connection connection = DriverManager.getConnection(url, user, password);
PreparedStatement preparedStatement = connection.prepareStatement("SELECT 1 FROM DUAL ");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
String string = resultSet.getString(1);
System.out.println(string);
}
connection.close();
}
}
答案是肯定的。
分析
这里可能会疑问,为什么我不需要注册驱动,但是程序能继续执行?
答案要从DriverManager.getConnection()方法说起,当jvm执行此代码时要做一件事。类加载流程,将DriverManager加载到jvm中,并执行初始化方法(静态代码块)。DriverManager中的静态代码块中代码如下
这一行只是创建了一个ServiceLoader实例,注意ServiceLoader实现了Iterable接口。所以要重写iterator方法
lookupIterator是ServiceLoader的成员变量,也是ServiceLoader的内部类
LazyIterator才是ServiceLoader的核心。因为真正触发类加载过程的就是迭代的过程。代码复制如下
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//获取文件全名 这里是/META-INF/services/java.sql.Driver String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
//通过类加载器获取到config对象,此时对象包含两个jar包下的
//文件,分别是mysql驱动包和druid数据源下驱动包
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
//pending是通过上面获取的文件,解析文件里面的内容获得
//驱动类的全名
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
对应mysql驱动包的加载文件如下
这些类名就是mysql驱动包的类全名。
LazyIterator迭代器的next()方法是真正开始将上面获取的类名加载的jvm的过程
最终初始化完成的实例对象会存到ServiceLoader中的providers中
结论
此时疑惑得到解答,我在DriverManage的文档注释中也找到了答案。
中间这段大概意思是当getConnection()方法被调用时,DriverManager会用当前应用的类加载器选择一个合适的数据库驱动加载。言外之意,新版本中不需要在显示的注册驱动了。
这里小伙伴可以了解下java中SPI机制。ServiceLoader是SPI机制的实现。可以通过定义接口,在不同的jar包实现接口,并以ServiceLoader规范的方式创建好相应文件。即可在应用中实现自动创建jar包中实现类的实例。这就是组件化,插件化实现的一种方式。
干货分享
最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!
•001:《Java并发与高并发解决方案》学习笔记;•002:《深入JVM内核——原理、诊断与优化》学习笔记;•003:《Java面试宝典》•004:《Docker开源书》•005:《Kubernetes开源书》•006:《DDD速成(领域驱动设计速成)》•007:全部•008:加技术群讨论
近期热文
•LinkedBlockingQueue vs ConcurrentLinkedQueue•解读Java 8 中为并发而生的 ConcurrentHashMap•Redis性能监控指标汇总•最全的DevOps工具集合,再也不怕选型了!•微服务架构下,解决数据库跨库查询的一些思路•聊聊大厂面试官必问的 MySQL 锁机制
关注我
喜欢就点个"在看"呗^_^