Java类加载一:Class.forName()和ClassLoader.getSystemClassLoader().loadClass()区别

原创 2016年08月30日 18:31:47

要想搞清楚这两个方法的区别,我们需要了解一下Class的加载过程。Class的加载过程分为三步:

  1. loading(装载)
  2. linking(链接)
  3. initializing(初始化)

大家可以通过这篇文章:Java魔法堂:类加载机制入了个门来了解类的详细加载过程。阅读以上文章后,我们一起分析一下两个方法的区别,如有不正之处,欢迎批评指正。

1、forName()

Class.forName()有两个重载的方法,都是public方法。

public static Class<?> forName(String className)
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)

public static Class

@CallerSensitive
public static Class<?> forName(String className) throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

public static Class

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 (loader == null) {
            ClassLoader ccl = ClassLoader.getClassLoader(caller);
            if (ccl != null) {
                sm.checkPermission(
                    SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
    }
    return forName0(name, initialize, loader, caller);
}

通过对比我们发现两个方法最终都调用了私有的forName0'方法,而'Class.forName(String className)方法中initialize参数默认为trueClass.forName(String name, boolean initialize, ClassLoader loader)方法的initialize参数由用户来指定。javadoc中关于该方法的initialized参数的说明如下:

@param initialize whether the class must be initialized
参数initialize 表示该类是否必须被初始化

通过initialize参数我们可以发现,该参数控制了类加载过程的第三步(初始化),该参数在’Class.forName(String className)方法中默认值为true`,因此在类加载的过程中会初始化类的相关信息,比如类中的静态块会被执行。因此我们得出结论:

Class.forName(className)

等同于

initialize = true;
Class.forName(className, initialize, loader)

示例代码:
ClassLoaderDemo1代码:

package com.ips.classloader;

public class ClassLoaderDemo1 {
    public static void main(String [] args){
        try {
            ClassLoader system = ClassLoader.getSystemClassLoader();
            Class<Config> cls = null;
            System.out.println("----------方法1----------");
            cls = (Class<Config>)Class.forName("com.ips.classloader.Config");

            System.out.println("----------方法2----------");
            cls = (Class<Config>)Class.forName("com.ips.classloader.Config", false, system);

            System.out.println("----------方法3----------");
            cls = (Class<Config>)Class.forName("com.ips.classloader.Config", true, system);

        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

Config代码:

package com.ips.classloader;

public class Config {
    private String name;

    private static boolean flag;
    static {
        flag = false;
        System.out.println("flag 的值为:" + flag);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

}

我们通过三种方式加载Config类,在Config类中有一段静态代码块,通过println函数来判定静态代码块是否被执行。
执行结果如下:

----------方法1----------
flag 的值为:false
----------方法2----------
----------方法3----------

我们发现方法3没有输出flag的值,这是为什么呢?原因是类加载过程中的缓存机制,由于方法1已经加载了该类,因此方法3不会再次加载该类,所以没有输出flag值,为了测试缓存的问题,我们将方法1与方法3的位置互换,程序的执行结果如下,可以看到方法3加载了该类,并且输出去了flag值,而方法1没有输出flag值。我们每次修改完代码都需要重启JVM来执行新的代码也是由类加载的缓存机制造成的。

----------方法3----------
flag 的值为:false
----------方法1----------
----------方法2----------

2、loadClass()

ClassLoader.getSystemClassLoader().loadClass()有两个重载方法,一个public方法,一个protected方法。

public Class<?> loadClass(String name) //方法1
protected Class<?> loadClass(String name, boolean resolve)  //方法2

public Class

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

protected Class

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 {
                    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();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

通过对比我们发现方法1调用了方法2,并且调用方法2的过程中,resolve参数的值为false。javadoc中关于该方法的resolve参数的说明如下:

@param  resolve If <tt>true</tt> then resolve the class
参数resolve 如果值为true则resolve这个类

我们看一下resolvetrue时,方法的执行逻辑

if (resolve) {
    resolveClass(c);
}

在看一下resolveClass方法

    /**
     * Links the specified class.  This (misleadingly named) method may be
     * used by a class loader to link a class.  If the class <tt>c</tt> has
     * already been linked, then this method simply returns. Otherwise, the
     * class is linked as described in the "Execution" chapter of
     * <cite>The Java&trade; Language Specification</cite>.
     * </p>
     *
     * @param  c
     *         The class to link
     *
     * @throws  NullPointerException
     *          If <tt>c</tt> is <tt>null</tt>.
     *
     * @see  #defineClass(String, byte[], int, int)
     */
    protected final void resolveClass(Class<?> c) {
        resolveClass0(c);
    }

从javadoc中我们可以看出,resolveClass方法主要是用来链接指定的类,通过resolve参数我们可以发现,该参数控制了类加载过程的第二步(链接),该参数值为false时不进行类的链接,为true时进行类的链接,由于loadClass(String name, boolean resolve)为protected方法,因此我们无法通过ClassLoader直接调用。
示例代码:

package com.ips.classloader;

public class ClassLoaderDemo1 {
    public static void main(String [] args){
        try {
            ClassLoader system = ClassLoader.getSystemClassLoader();
            Class<Config> cls = null;

            System.out.println("-----方法4-----");
            cls = (Class<Config>)ClassLoader.getSystemClassLoader().loadClass("com.ips.classloader.Config");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

-----方法4-----

没有执行Config类的静态代码块,由此可见Config只是进行了装载,没有进行链接与初始化。

3、关于mysql jdbc

我们在进行数据库操作的时候,通常采用如下的方式加载数据库驱动。

Class.forName("com.mysql.jdbc.Driver"); 

为什么不是ClassLoader.getSystemClassLoader().loadClass()呢?这是因为Driver类中的静态代码块需要进行一些初始化配置。代码如下:

 Copyright  2002-2004 MySQL AB, 2008 Sun Microsystems
package com.mysql.jdbc;

import java.sql.SQLException;

/**
 * The Java SQL framework allows for multiple database drivers. Each driver
 * should supply a class that implements the Driver interface
 * 
 * <p>
 * The DriverManager will try to load as many drivers as it can find and then
 * for any given connection request, it will ask each driver in turn to try to
 * connect to the target URL.
 * 
 * <p>
 * It is strongly recommended that each Driver class should be small and
 * standalone so that the Driver class can be loaded and queried without
 * bringing in vast quantities of supporting code.
 * 
 * <p>
 * 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 doing Class.forName("foo.bah.Driver")
 * 
 * @see org.gjt.mm.mysql.Connection
 * @see java.sql.Driver
 * @author Mark Matthews
 * @version $Id$
 */
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    // ~ Static fields/initializers
    // ---------------------------------------------

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

    // ~ Constructors
    // -----------------------------------------------------------

    /**
     * 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()
    }
}

4、总结

  • Class.forName() 方法中,initialize参数控制类在加载的过程中是否进行初始化。
  • ClassLoader.getSystemClassLoader().loadClass()方法中,resolve参数控制类在加载的过程中是否进行链接。
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

getClassLoader 和 ClassLoader.getSystemClassLoader()区别

1. getClassLoader 是在apk内部加载的 2.ClassLoader.getSystemClassLoader() 是系统级别。 liz...

Class.forName()和ClassLoader.getSystemClassLoader().loadClass()区别

class A{ static {  System.out.println("Class A is Loading now"); } public A() {  System.out.println(...

Java ClassLoader深入讲解

当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:       bootstrap classloader                |       extensi...

java中class.forName和classLoader加载类的区分

java中class.forName和classLoader都可用来对类进行加载。前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。而classLoader...

Class.forName和ClassLoader.loadClass的比较

Class的装载分了三个阶段,loading,linking和initializing,分别定义在The Java Language Specification的12.2,12.3和12.4。Clas...
  • lu7kang
  • lu7kang
  • 2010年05月10日 18:54
  • 10097

探讨Class.forName与ClassLoader.loadClass与new Object

探讨Class.forName与ClassLoader.loadClass与new Object探讨ClassforName与ClassLoaderloadClass与new Object 实例展示 ...

深入分析Java ClassLoader原理

一、什么是ClassLoader?          大家都知道,当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运...
  • xyang81
  • xyang81
  • 2012年02月25日 21:39
  • 97413

this.class.getClassLoader()怎么理解?

this.class.getClassLoader()怎么理解? java是面向对象语言,面向对象的语言的宗旨就是万事万物皆对象,那么类也是一个对象,类里面的属性和方法也是对象。java里面的所...
  • zztp01
  • zztp01
  • 2011年05月10日 15:40
  • 36321

一看你就懂,超详细java中的ClassLoader详解

ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLo...
  • briblue
  • briblue
  • 2017年02月10日 19:26
  • 30465

class.getClassLoader().getResource()和class.getResource()的不同

1.两者都是读取配置文件的方式。前者是正规的实现,即先由class获得classLoader,再由classLoader获得文件。后者是封装的一个方法,二者的区别在哪呢?在参数上。2.二者的参数类型都...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java类加载一:Class.forName()和ClassLoader.getSystemClassLoader().loadClass()区别
举报原因:
原因补充:

(最多只允许输入30个字)