Java知识记录(一)为什么Class.forName("com.mysql.jdbc.Driver") ?

初见问题

在学习注解的时候,我遇到了一个自定义注解模拟DBUtil的案例。

package util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtil {
    static String ip = "127.0.0.1";
    static int port = 3306;
    static String database = "test";
    static String encoding = "UTF-8";
    static String loginName = "root";
    static String password = "admin";
    static{
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
        public static Connection getConnection() throws SQLException {
        String url = String.format("jdbc:mysql://%s:%d/%s?characterEncoding=%s", ip, port, database, encoding);
      return DriverManager.getConnection(url, loginName, password);
}
        public static void main(String[] args) throws SQLException {
        System.out.println(getConnection());
    }
}   

其中Class.forName("com.mysql.jdbc.Driver"),这句话引起了我的注意。为什么这里为什么会平白无故的创建一个Class呢,而且为什么只是创建对象,而没有实例化呢?这个问题一下子带出了很多有意思的内容。

对象的创建

首先我们回顾一下创建类的两种方式:new以及反射。

通过new的方式创建一个类,这是我们最常见的一种方式。通过这种方式,我们可以调用任何是public的构造方法,它相对反射创建可以很高效的创建一个类。

而通过反射的方式,我们同样可以创建一个类。我们可以通过Class.forName()的方式获取一个类对象。然后通过将这个类newInstance()实例化,完成一个对象的创建。这里返回的对象为Object,所以我们通过需要做一个类型转化,来完成真正意义上的对象创建。还有另一种方式,就是通过ClassLoader.loadCLass()的方式来加载类,随后再实例化。

而问题的答案其实就存在于通过反射创建对象的过程当中。联系到JVM的知识,我们回忆一下,JVM是如何加载类的。

类的加载

首先我们要知道,JVM运行字节码需要将代码加载到内存中去。将class文件加载到内存中需要以下三个步骤:加载、链接、初始化。这个问题的核心在于初始化,我们不妨再大概回顾一下前两步的内容。

加载的意义在于查询字节流,并据此创建类。其中涉及到的知识点还有boot classloader启动类加载器,这玩意儿是C++写的,Java肯定访问不了的,但是我们必须有它才可以把一些顶级类加载器加载到JVM中,再进行将其他类加载进来。说到这里就又要提双亲委派模型了:低级类加载器应该先让它的上级类加载器查询后才确认是否干活。再需要说的就是类的唯一性了,我们可以借助不同的类加载器,来实现一个类的不同实现。这是由类加载器提供的命名空间实现的。

说完加载,链接的过程相对简单一点了。链接的主要作用就是把我们上一步中加载的类链接起来。它具体又有三个步骤:验证、准备、解析。但是解析阶段是非必须的。

最后回到我们问题的答案上,初始化阶段干了什么?给常量赋值以及执行静态代码块。我们看一下什么情况下会触发初始化。

  1. 当虚拟机启动时,初始化用户指定的主类;
  2. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类;
  3. 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
  4. 当遇到访问静态字段的指令时,初始化该静态字段所在的类;
  5. 子类的初始化会触发父类的初始化;
  6. 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
  7. 使用反射 API 对某个类进行反射调用时,初始化这个类;
  8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。

通过上面的事例,我们大概可以猜测到之所以用Class.forName()的原因是在于要执行某些静态代码块或者为某些常量赋值。我们先解答一下为什么要用Class.forName(),而不是new和ClassLoader.loadClass()的问题。

  1. new开销太大,我们其实只需要执行某个静态代码块而已,并不需要将整个对象创建
  2. ClassLoader.loadClass()并没有将对象初始化,也就是没有执行我们需要的静态代码块

DriverManager

最后我们来揭晓一下这个神秘的静态代码块到底是什么。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());//首先new一个Driver对象,并将它注册到DriverManage中
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己。之所以使用Class.forName("com.mysql.jdbc.Driver")的原因,就是为了执行这个静态代码块,使Driver在DriverManager中注册。

而在高版本的JDK中,我们已经不需要在手动的调用这种方法,在DriverManager的源码中有这么一个静态块。

/**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

让我们来探究一下loadInitialDrivers()的作用。

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction() {
            public Void run() {

                ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

重点是ServiceLoader.load(Driver.class)这行代码。

上面这行代码可以把类路径下所有jar包中META-INF/services/java.sql.Driver文件中定义的类加载上来,此类必须继承自java.sql.Driver。

最后我们看一下Iterator的next()方法做了什么就完全懂了,通过next()方法调用了:

private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class c = null;
    try {
        c = Class.forName(cn, false, loader); //看这里,Class.forName()
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
   }
    if (!service.isAssignableFrom(c)) {
       fail(service,
             "Provider " + cn  + " not a subtype");
    }
    try {
        S p = service.cast(c.newInstance());
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
            x);
    }
    throw new Error();          // This cannot happen
}

说白了就是把原来我们手动的方式变成了框架自动的方式。这种方式也叫做SPI。关于SPI的知识我们可以自行去查询学习一下,也是挺有意思的。

你可能感兴趣的:(Java知识记录(一)为什么Class.forName("com.mysql.jdbc.Driver") ?)