您的位置:首页 > 编程语言 > Java开发

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

2016-08-30 18:31 411 查看
要想搞清楚这两个方法的区别,我们需要了解一下Class的加载过程。Class的加载过程分为三步:

loading(装载)

linking(链接)

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
参数默认为
true
Class.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这个类


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

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™ 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
参数控制类在加载的过程中是否进行链接。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐