使用ClassLoader和Class.forName加载类之间的区别

时间:2010-11-26 13:56:00

标签: java reflection classloader

以下是2个代码段

第一个使用ClassLoader类加载指定的类

ClassLoader cls = ClassLoader.getSystemClassLoader(); Class someClass = cls.loadClass("TargetClass");

第二个使用Class.forName()加载指定的类

Class cls = Class.forName("TargetClass");

上述方法之间有什么区别。哪一个用于哪个目的?

9 个答案:

答案 0 :(得分:46)

其他答案非常完整,因为他们探索Class.forName(...)的其他重载,并讨论使用不同ClassLoader的可能性。

然而,他们未能回答您的直接问题:“上述方法之间有什么区别?”,它处理Class.forName(...)的一个特定重载。他们错过了一个非常重要的区别。 类初始化

考虑以下课程:

public class A {
  static { System.out.println("time = " + System.currentTimeMillis()); }
}

现在考虑以下两种方法:

public class Main1 {
  public static void main(String... args) throws Throwable {
    final Class<?> c = Class.forName("A");
  }
}

public class Main2 {
  public static void main(String... args) throws Throwable {
    ClassLoader.getSystemClassLoader().loadClass("A");
  }
}

第一个类Main1在运行时会产生输出,例如

time = 1313614183558
但是,另一个根本不会产生任何输出。这意味着类A虽然已加载,但尚未初始化(即,<clinit>未被调用)。实际上,您甚至可以在初始化之前通过反射查询类的成员!

你为什么要关心?

有些类在初始化时执行某种重要的初始化或注册。

例如,JDBC指定由不同提供程序实现的接口。要使用MySQL,通常需要Class.forName("com.mysql.jdbc.Driver");。也就是说,您加载并初始化类。我从来没有见过那些代码,但显然该类的静态构造函数必须在JDBC的某处注册类(或其他东西)。

如果你做ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");,你将无法使用JDBC,因为尚未初始化已加载的类(然后JDBC不知道要使用哪个实现,就像你有没有加载类。)

所以,这就是你提出的两种方法之间的区别。

答案 1 :(得分:14)

快速回答(无代码示例)

使用显式ClassLoader cls = <a ClassLoader>;方法,您可以灵活地从ClassLoader加载类,该类是而不是您的默认ClassLoader。在您的情况下,您使用的是默认的System ClassLoader,因此它会给Class.forName(String name)调用提供类似的整体结果(最终对象差异的实例化),但可以引用另一个而不是ClassLoader。

也就是说,只要您知道ClassLoader是什么,您也可以使用Class.forName(String name, boolean initialize, ClassLoader loader)

例如,您的基于EAR的应用程序有自己的ClassLoader,其中包含一个XML Parsing库的版本。您的代码通常使用这些类,但在一个实例中,您需要从库的早期版本中获取反序列化类(Application Server恰好在整体ClassLoader中)。因此,您可以改为引用Application Server ClassLoader。

不幸的是,在我们获得项目Jigsaw(JDK 8)之前,这种情况的使用频率超出了我们的预期: - )

答案 2 :(得分:12)

在你的具体案例中:

ClassLoader cls = ClassLoader.getSystemClassLoader();
Class someClass = cls.loadClass("TargetClass");

以上代码将TargetClass加载Class cls = Class.forName("TargetClass"); 始终

TargetClass

第二个代码段将使用用于加载正在执行该行代码的类的类加载器加载(并初始化)ClassLoader.loadClass()。如果该类加载了系统类加载器,则这两种方法是相同的(除了类初始化,如Bruno的优秀答案中所述)。

使用哪一个?为了使用反射加载和检查类,我建议使用特定的类加载器(Class.forName(String, true, ClassLoader)) - 它会让您掌控并有助于避免可能隐藏的问题在不同的环境之间。

如果您需要加载AND初始化,请使用Class.getClassLoader()

如何找到合适的类加载器?这取决于您的环境:

  • 如果您正在运行命令行应用程序,则可以使用system classloader或加载应用程序类的类加载器(ClassUtils.forName())。
  • 如果您在托管环境(JavaEE,servlet容器等)中运行,那么最好先检查system classloader,然后再回到前一点给出的选项。
  • 或者只使用您自己的自定义类加载器(如果您涉及到这类事情)

一般情况下,最傻瓜并经过测试的方法是使用Spring的Class.forName()(参见current thread context class loader)。

更深入的解释:


  

最常见的String形式,即采用单个forName()参数的形式,始终使用调用者的类加载器。这是加载执行ClassLoader.loadClass()方法的代码的类加载器。相比之下,ClassLoader.loadClass()是一个实例方法,需要您选择一个特定的类加载器,它可能是也可能不是加载该调用代码的加载器。如果选择特定加载器来加载类对您的设计很重要,则应使用{em> Java 2平台标准版(J2SE)中添加的forName()或三参数版本的Class.forName(String, boolean, ClassLoader) < / em>:Class.forName(String, boolean, ClassLoader)

来源:JavaDoc


此外,What is the difference between Class.forName() and ClassLoader.loadClass()?在使用{{1}}时突出显示了一个有趣的模糊角落。

正如Spring问题所示,使用ClassLoader.loadClass()是推荐的方法(当你需要从特定的类加载器加载类时)。

答案 3 :(得分:1)

ClassLoader.loadClass()使用指定的类加载器(在您的情况下为系统类加载器),而Class.forName()使用当前类的类加载器。

当您不关心特定的类加载器并且希望与静态引用的类具有相同的类加载行为时,可以使用

Class.forName()

答案 4 :(得分:1)

来自API doc

  

调用此方法相当于:

  Class.forName(className, true, currentLoader)
     

其中currentLoader表示当前的定义类加载器   类。

所以主要区别在于将使用哪个类加载器(它可能与系统类加载器相同或不同)。 重载方法还允许您指定要显式使用的类加载器。

答案 5 :(得分:1)

ClassLoader.loadClass()总是加载系统类加载器,而Class.forName()则加载任何类。让我们看看这个例子,

package com;
public class TimeA {
      public static void main (String args[]) {
            try {
                final Class c = Class.forName("com.A");
                ClassLoader.getSystemClassLoader().loadClass("com.A");
            }catch(ClassNotFoundException ex) {
                System.out.println(ex.toString());
            }
      }
}

class A {
      static {
          System.out.println("time = " + System.currentTimeMillis()); 
      }
}

当你运行这个程序时,你会在ClassLoader.getSystemClassLoader().loadClass("com.A");

得到一个例外

输出可能是:

time = 1388864219803
java.lang.ClassNotFoundException: com.A

答案 6 :(得分:0)

第二种方法使用ClassLoader

加载一个类
 public static Class<?> forName(String className) 
                throws ClassNotFoundException {
        return forName0(className, true, ClassLoader.getCallerClassLoader());

这就是JavaDoc所说的:

forName(String name, boolean initialize, ClassLoader loader)
  

指定的类加载器用于   加载类或接口。如果   参数loader是null,类是   通过bootstrap类加载   加载器。

因此,第二个选项使用System ClassLoader(实际上,它在第一个选项中的作用)。

答案 7 :(得分:0)

加载数组类型时也有区别。我认为classloader.loadClass(clazz)无法处理数组类型,但Class.forName(clazz,true,classloader)可以。

答案 8 :(得分:0)

但静态initilizer块只在我们使用class.forname时执行(&#34; ...&#34;);

我刚刚测试过。