zoukankan      html  css  js  c++  java
  • 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、参考

  • 相关阅读:
    mysql install steps
    d3js
    js布局库
    mac 学习笔记
    js图形库
    zeromq 笔记
    C语言程序员必读的5本书
    Java基础
    JS中的toString方法
    给你六种面额1 5 10 20 50 100元的纸币假设每种币值的数量足够多
  • 原文地址:https://www.cnblogs.com/wpbxin/p/14904980.html
Copyright © 2011-2022 走看看