如果熟悉使用 JDBC 来连接数据库的同学一定很清楚连接数据库的代码中一定会有依据 Class.forName("com.mysql.jdbc.Driver");
- public static Connection getConnection() throws ClassNotFoundException, SQLException {
- if(connection == null){
- Class.forName("com.mysql.jdbc.Driver");
- connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?serverTimezone=UTC", "root", "xxxxxx");
- }
- return connection;
- }
之前没有想过为什么需要有这么一个语句, 都是按照文档直接这么做的, 在这篇文章中我来试着解释这么做的原因.
类加载机制
在这之前我们先来说下 Java 中的类加载机制.
在 Java 中如果想要使用一个类, 则必须要求该类已经被加载到 Jvm 中, 加载的过程实际上就是通过类的全限定名来获取定义该类二进制字节流, 然后将这个字节流所表示的静态存储结构转换为方法去的动态运行时数据结构. 同时在在内存中实例化一个 java.lang.Class 对象, 作为方法区中该类的数据访问入口(供我们使用).
而会触发类加载的会有如下几种情况(引用自<<深入理解 Java 虚拟机>>):
遇到 new,getstatic,putstatic 或 invokestatic 这 4 条字节码指令时, 如果类没有进行过初始化, 则需要先触发其初始化. 生成这 4 条指令的最常见的 Java 代码场景是: 使用 new 关键字实例化对象的时候, 读取或设置一个类的静态字段 (被 final 修饰, 已在编译期把结果放入常量池的静态字段除外) 的时候, 以及调用一个类的静态方法的时候.
使用 java.lang.reflect 包的方法对类进行反射调用的时候, 如果类没有进行过初始化, 则需要先触发其初始化.
当初始化一个类的时候, 如果发现其父类还没有进行过初始化, 则需要先触发其父类的初始化.
当虚拟机启动时, 用户需要指定一个要执行的主类 (包含 main() 方法的那个类), 虚拟机会先初始化这个主类.
Class.forName
在 Java 官方文档中对 Class.forName 的解释为在运行时动态的加载一个类, 返回值为生成的 Class 对象.
那么很明显在 jdbc 中使用 Class.forName("com.mysql.jdbc.Driver"); 仅仅就是将 com.MySQL.jdbc.Driver 类加载到 Jvm 中了, 这个原因大多数人应该都知道.
但是我们要知道 Class.forName 貌似只是对类进行了加载, 我们甚至都没有对返回的 Class 对象做任何操作, 那么我们为什么后面就可以直接用了呢?
首先看 Class.forName 调用了 native 方法 forName0(...);
- @CallerSensitive
- public static Class<?> forName(String className)
- throws ClassNotFoundException {
- Class<?> caller = Reflection.getCallerClass();
- return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
- }
- private static native Class<?> forName0(String name, boolean initialize,ClassLoader loader,Class<?> caller);
要注意 forName0 中有一个关键的参数 boolean initialize,; 该参数用来标识在将该类加载后是否进行初始化操作. 可以看到代码中是 true, 就表示会进行初始化操作.
初始化过程实际上就是对变量赋值 (不是赋初值, 不会调用构造函数) 的过程. 包含所有类变量的赋值以及静态代码语句块的执行代码, 包括对父类的初始化.
再看 com.MySQL.jdbc.Driver 驱动类:
- 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!");
- }
- }
- }
该类中定义了一个静态代码块, 静态代码快中创建了一个驱动类实例注册给了 DriverManager, 而静态代码块的内容会在初始化的过程中执行, 所以才能通过 DriverManager.getConnection 直接获取一个连接.
其他加载类方法
我们需要明白的是在 Java 中并不是只有通过 Class.forName()才能显示的加载类. 那么为什么不使用其他的加载方法而偏偏选择 Class.forName()呢?
ClassLoader.getSystemClassLoader().loadClass()
通过类加载器也可以将一个类加载到 Jvm 中. 通过 ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver"); 也可以加载驱动类.
但是如果我们深入看下 loadClass 的实现:
- public Class<?> loadClass(String name) throws ClassNotFoundException {
- return loadClass(name, false);
- }
- protected Class<?> loadClass(String name, boolean resolve);
可以看到其调用了一个重载的方法, 该方法也有一个 boolean 类型的变量 boolean resolve, 调用时默认为 false. 该参数用于标识是否对加载后的类进行链接操作, 如果不进行连接操作则不会有初始化的操作.
所以如果使用这种加载类方式的话理论上来说是没发使用该驱动类的.
new 关键字
也可以使用 new 关键字进行加载操作, 在使用 new 关键字时会查看该类是否已经被加载, 如果没有被加载的话则会进行加载操作. 所以我们的类中也可以这样写:
- public static Connection getConnection() throws ClassNotFoundException, SQLException {
- if(connection == null){
- new Driver();// 会自动调用静态代码块
- connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?serverTimezone=UTC", "root", "xxxx");
- }
- return connection;
- }
但是实际上因为在驱动类的静态代码快中实际上已经有了实例化对象并注册到 DriverMananger 中的操作. 所以这里根本就没有在实例化一个对象的过程. 使用 Class.forName 即可, 这也算是一个优化的过程吧.
可以不使用 Class.forName("com.mysql.jdbc.Driver")
在测试的过程中发现即使不显示的使用 Class.forName("com.mysql.jdbc.Driver")也能够连接到数据库, 一时间觉得很奇怪.
深入跟踪代码后发现实际上只要我们引入了 MySQL 的驱动包, 那么在使用时会根据驱动包下提供的配置文件默认的创建一个类.
所以实际上只要引入了该驱动包, 那么使用 jdbc 是可以直接通过 DriverManage 来获取连接.
- public static Connection getConnection() SQLException {
- return DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?serverTimezone=UTC", "root", "xxxxxx");
- }
来源: https://www.cnblogs.com/liyus/p/10634554.html