Java基础(018):Class.forName和ClassLoader的区别

  注:这个问题,其实到网上一搜就一大把相关的说明文章,讲的也差不多。那为什么笔者还要花时间整理呢?首先是作为自己知识体系的一个整理和总结,其次是想大致说明白写的这些东西到底是哪来的,然后尽可能多地贴出相关的来源链接(其实大部分就是官方权威说明文档)供大家参考和学习,还原出这个推断的过程,最后想表达的还是同样一个意思:接口说明尽可能看官方文档或者源码,可以说是基本解释得七七八八。总之,最重要的是自己思考和求证、总结的过程,获得收获的是自己。

  本篇目录结构如下:

1、Class.forName和ClassLoader的区别

  回归正文,笔者认为,这个问题应该得再说具体一点,譬如: Class.forName(String className) 方法和 ClassLoader.loadClass(String name) 的区别。因为可以实现与之对等功能的方法有 Class.forName(String name, boolean initialize, ClassLoader loader)ClassLoader.loadClass(String name, boolean resolve)loadClass(String name) 内部就是直接调用了 #loadClass(name, false) 进行处理),后面会对此进行分析。

  这里直接先上结论,毕竟这是API接口功能的差异,很多人都已经知道结论了,也不是什么新鲜事:

  • ClassLoader.loadClass(String name) 只会找对应的 class 字节码并加载(Loading)到JVM中,不会干其他的事,例如链接(Linking)、初始化(Initialization)等都不会再进一步处理。
  • Class.forName(String className) 不仅会找对应的 class 字节码并加载(Loading)到JVM中,还会进行链接(Linking)、初始化(Initialization),执行类的静态代码块和静态变量的初始化。

  示例(参考笔者 github):

package cn.wpbxin.javabasis.reflection;

/**
 * 用于验证 ClassForName 和 ClassLoader 的实例类
 * @author wpbxin
 *
 */
public class ClassForNameInstance {
    public static int i = 1;
    
    // 普通代码块
    {
        System.out.println("instance block");
    }
    
    // 静态代码块
    static {
        System.out.println("static block executes:");
        System.out.println("i=" + i);
    }
    
    // 静态方法
    public static void staticMethod() {
        System.out.println("staticMethod invoke");
    }
}

/// 测试类
package cn.wpbxin.javabasis.reflection;

/**
 * <tt>Class.forName</tt> 和 <tt>ClassLoader</tt> 的区别比较
 * @author wpbxin
 *
 */
public class ClassForNameAndClassLoader {

    /**
     * 
     * @param args
     * @throws ClassNotFoundException
     */
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println("+++++++++++++++++++++++++++++++++");
        String name = "cn.wpbxin.javabasis.reflection.ClassForNameInstance";
        
        System.out.println("ClassLoader.loadClass");
        Class<?> loaderInstance = ClassLoader.getSystemClassLoader().loadClass(name);
        System.out.println("class name = " + loaderInstance.getName());
        
        System.out.println("---------------------------------");
        
        System.out.println("Class.forName");
        Class<?> fornameInstance = Class.forName(name);
        System.out.println("class name = " + fornameInstance.getName());
    }

}

  

类加载机制

  仔细观察,上面提到的其实就是大家比较熟悉的类加载过程:加载、链接、初始化,接着便是实例化和使用,然后就是实例对象的清理回收(内存回收),最后就是类的卸载。这里提到的其实就是前面的3个步骤: 加载、链接 和 初始化ClassLoader.loadClass(String name) 只做了第一步,而 Class.forName(String className) 则都处理了。整个过程大概就是下面这张大家常见的图示(图片来源于网络)。

  至于为何是这个过程,其实可以说是JVM的规范之一,具体可以参考下 [1]Chapter 12. Execution 中提到的Java程序应用的一整个完整的执行流程,或者也可以硬刚虚拟机规范《The Java® Virtual Machine Specification Java SE 8 Edition》中的相关说明 [5]Chapter 5. Loading, Linking, and Initializing 。这么一看的话,这个图其实就是对这个过程的一个总结。

  具体的类加载过程说明和分析,除了参考上面给出的官方链接外,也强烈建议参考《深入理解Java虚拟机》第三版,笔者这里就不赘述了。

2、Class.forName和ClassLoader的差异分析

  下面我们来看下具体的源码和API说明,理清差异的原因。

  Class.forName(String className) 接口源码如下:

/**
 * Returns the {@code Class} object associated with the class or
 * interface with the given string name.  Invoking this method is
 * equivalent to:
 *
 * <blockquote>
 *  {@code Class.forName(className, true, currentLoader)}
 * </blockquote>
 *
 * where {@code currentLoader} denotes the defining class loader of
 * the current class.
 *
 * <p> For example, the following code fragment returns the
 * runtime {@code Class} descriptor for the class named
 * {@code java.lang.Thread}:
 *
 * <blockquote>
 *   {@code Class t = Class.forName("java.lang.Thread")}
 * </blockquote>
 * <p>
 * A call to {@code forName("X")} causes the class named
 * {@code X} to be initialized.
 *
 * @param      className   the fully qualified name of the desired class.
 * @return     the {@code Class} object for the class with the
 *             specified name.
 * @exception LinkageError if the linkage fails
 * @exception ExceptionInInitializerError if the initialization provoked
 *            by this method fails
 * @exception ClassNotFoundException if the class cannot be located
 */
@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

  其中的注释说明,我这里重新贴一下,方便看:


 Returns the Class object associated with the class or interface with the given string name. Invoking this method is equivalent to:

  Class.forName(className, true, currentLoader)
where currentLoader denotes the defining class loader of the current class.
For example, the following code fragment returns the runtime Class descriptor for the class named java.lang.Thread:
  Class t = Class.forName("java.lang.Thread")
A call to forName("X") causes the class named X to be initialized. 


  从注释可以看出, Class.forName(String className) 的调用其实等价于 Class.forName(className, true, currentLoader) 。这两最终都是调用了本地方法 forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) ,这个调用通过将参数 initialize 设置为 true 来指定进行类的初始化,而类的初始化过程会调用静态代码块。既然会进行初始化,那肯定会在初始化之前进行加载和链接的。

  Class.forName(String name, boolean initialize, ClassLoader loader) 接口源码如下,这里我们可以看到参数 initialize 用于控制是否进行初始化,false 表示只进行加载和链接,true表示还需要进行初始化,而参数 ClassLoader 则是用于进行类加载的。

/**
 * Returns the {@code Class} object associated with the class or
 * interface with the given string name, using the given class loader.
 * Given the fully qualified name for a class or interface (in the same
 * format returned by {@code getName}) this method attempts to
 * locate, load, and link the class or interface.  The specified class
 * loader is used to load the class or interface.  If the parameter
 * {@code loader} is null, the class is loaded through the bootstrap
 * class loader.  The class is initialized only if the
 * {@code initialize} parameter is {@code true} and if it has
 * not been initialized earlier.
 *
 * <p> If {@code name} denotes a primitive type or void, an attempt
 * will be made to locate a user-defined class in the unnamed package whose
 * name is {@code name}. Therefore, this method cannot be used to
 * obtain any of the {@code Class} objects representing primitive
 * types or void.
 *
 * <p> If {@code name} denotes an array class, the component type of
 * the array class is loaded but not initialized.
 *
 * <p> For example, in an instance method the expression:
 *
 * <blockquote>
 *  {@code Class.forName("Foo")}
 * </blockquote>
 *
 * is equivalent to:
 *
 * <blockquote>
 *  {@code Class.forName("Foo", true, this.getClass().getClassLoader())}
 * </blockquote>
 *
 * Note that this method throws errors related to loading, linking or
 * initializing as specified in Sections 12.2, 12.3 and 12.4 of <em>The
 * Java Language Specification</em>.
 * Note that this method does not check whether the requested class
 * is accessible to its caller.
 *
 * <p> If the {@code loader} is {@code null}, and a security
 * manager is present, and the caller's class loader is not null, then this
 * method calls the security manager's {@code checkPermission} method
 * with a {@code RuntimePermission("getClassLoader")} permission to
 * ensure it's ok to access the bootstrap class loader.
 *
 * @param name       fully qualified name of the desired class
 * @param initialize if {@code true} the class will be initialized.
 *                   See Section 12.4 of <em>The Java Language Specification</em>.
 * @param loader     class loader from which the class must be loaded
 * @return           class object representing the desired class
 *
 * @exception LinkageError if the linkage fails
 * @exception ExceptionInInitializerError if the initialization provoked
 *            by this method fails
 * @exception ClassNotFoundException if the class cannot be located by
 *            the specified class loader
 *
 * @see       java.lang.Class#forName(String)
 * @see       java.lang.ClassLoader
 * @since     1.2
 */
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
                               ClassLoader loader)
    throws ClassNotFoundException
{
    Class<?> caller = null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        // Reflective call to get caller class is only needed if a security manager
        // is present.  Avoid the overhead of making this call otherwise.
        caller = Reflection.getCallerClass();
        if (sun.misc.VM.isSystemDomainLoader(loader)) {
            ClassLoader ccl = ClassLoader.getClassLoader(caller);
            if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                sm.checkPermission(
                    SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
    }
    return forName0(name, initialize, loader, caller);
}

  接下来我们来看下 ClassLoader 。

  ClassLoader 则是大家常说的类加载器,它遵循双亲委派模型进行类的加载,最终会调用启动类加载器的类加载器,类加载器实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中。ClassLoader.loadClass(String name) 中的参数 name 是官方文档提到的 binary name ,具体说明参考 [6]Binary names ,也可以当作就是类的全限定名(the fully qualified name)

  ClassLoader.loadClass(String name)ClassLoader.loadClass(String name, boolean resolve) 的源码如下

/**
 * Loads the class with the specified <a href="#name">binary name</a>.
 * This method searches for classes in the same manner as the {@link
 * #loadClass(String, boolean)} method.  It is invoked by the Java virtual
 * machine to resolve class references.  Invoking this method is equivalent
 * to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,
 * false)</tt>}.
 *
 * @param  name
 *         The <a href="#name">binary name</a> of the class
 *
 * @return  The resulting <tt>Class</tt> object
 *
 * @throws  ClassNotFoundException
 *          If the class was not found
 */
public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

/**
 * Loads the class with the specified <a href="#name">binary name</a>.  The
 * default implementation of this method searches for classes in the
 * following order:
 *
 * <ol>
 *
 *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
 *   has already been loaded.  </p></li>
 *
 *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
 *   on the parent class loader.  If the parent is <tt>null</tt> the class
 *   loader built-in to the virtual machine is used, instead.  </p></li>
 *
 *   <li><p> Invoke the {@link #findClass(String)} method to find the
 *   class.  </p></li>
 *
 * </ol>
 *
 * <p> If the class was found using the above steps, and the
 * <tt>resolve</tt> flag is true, this method will then invoke the {@link
 * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
 *
 * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
 * #findClass(String)}, rather than this method.  </p>
 *
 * <p> Unless overridden, this method synchronizes on the result of
 * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
 * during the entire class loading process.
 *
 * @param  name
 *         The <a href="#name">binary name</a> of the class
 *
 * @param  resolve
 *         If <tt>true</tt> then resolve the class
 *
 * @return  The resulting <tt>Class</tt> object
 *
 * @throws  ClassNotFoundException
 *          If the class could not be found
 */
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        // 检查是否已经加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 有父类加载器则委托个父类加载器加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 没有父类加载器的委托给 BootstrapClassLoader
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                // 当前类加载器加载
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        // 是否需要进行解析,也就是链接 Links the specified class.
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

  可以看到 ClassLoader.loadClass(String name) 方法只做了加载的操作,实际上通过委托给 ClassLoader.loadClass(String name, boolean resolve) 进行加载,参数 resolve=false 表示不会进行链接,更别说是初始化了。从这里的代码中我们也可以看到,ClassLoader 加载起加载前会委托给父类加载器来处理,最终则是没有父类的 BootstrapClassLoader 进行加载,如果都找不到,就由当前的类加载器来加载。这和我们已经了解到的双亲委派模型加载是基本对应的。

  这里也可以看到,想要破坏双亲委派模型的话,其实就是省略掉委托给父类加载器加载的过程,直接自己加载,这样就可以在不同的类加载器中拥有“一样”的类而又彼此隔离互不干扰。

3、Class.forName应用之JDBC驱动的的加载

  学习 JDBC 的时候,我们经常可以看到这样一个统一的操作流程:

// 注册数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取数据库连接
Connection conn = DriverManager.getConnection(url,username,password);
PreparedStatement ps = conn.prepareStatement(sql);
/// ...


// 数据库连接的第一步:先加载需要连接的数据库驱动到 JVM
// 通过静态方法 java.lang.Class#forName(String className) 来实现。
try {
    // 加载 MySQL 的驱动类
    Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {   
    // something to do here
    e.printStackTrace();
}   
// 成功加载后,会将 Driver 类的实例注册到 DriverManager 类中
// 接下来进行其他操作。。。
// Connection connection = DriverManager.getConnection(url, username, password); 

  这里调用了 Class.forName(drivername) 之后就可以直接操作了,为什么就能够直接使用了呢?

  JDBC规范中要求这个 Driver 实现类必须向 DriverManager 注册自己的一个实例,(以下来自 java.sql.Driver 文档说明)


When a Driver class is loaded, it should create an instance of itself and register it with the DriverManager. This means that a user can load and register a driver by calling:

  Class.forName("foo.bah.Driver")


  从上面我们可以知道,Class.forName 调用之后会进行初始化,而初始化则会执行静态代码块,因此 Driver 实现类通过静态代码块来注册驱动实例,就可以达到上面的要求,即任何一个 JDBC Driver 的 Driver 实现类基本都会有如下的类似代码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

  至此,我们知道了为何只写 Class.forName(drivername) 后就可以直接使用数据库驱动了。

4、参考

原文地址:https://www.cnblogs.com/wpbxin/p/14904980.html