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

6 篇文章 0 订阅

要想搞清楚这两个方法的区别,我们需要了解一下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)
 
 
  • 1
  • 2

public static Class

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

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);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

通过对比我们发现两个方法最终都调用了私有的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 表示该类是否必须被初始化
 
 
  • 1
  • 2

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

Class.forName(className)
 
 
  • 1

等同于

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

示例代码: 
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();
        }
    }
}


 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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;
    }

}


 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

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

 
 
  • 1
  • 2
  • 3
  • 4
  • 5

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

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


 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、loadClass()

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

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

public Class

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

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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

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

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

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

if (resolve) {
    resolveClass(c);
}
 
 
  • 1
  • 2
  • 3

在看一下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);
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

从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();
        }
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

执行结果:

-----方法4-----
 
 
  • 1

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

3、关于mysql jdbc

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

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

为什么不是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()
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

4、总结

  • Class.forName() 方法中,initialize参数控制类在加载的过程中是否进行初始化。
  • ClassLoader.getSystemClassLoader().loadClass()方法中,resolve参数控制类在加载的过程中是否进行链接。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值