【问题标题】:Difference between Loading a class using ClassLoader and Class.forName使用 ClassLoader 和 Class.forName 加载类的区别
【发布时间】:2023-04-03 04:45:02
【问题描述】:

下面是2个代码sn-ps

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

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

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

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

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

【问题讨论】:

    标签: java reflection classloader


    【解决方案1】:

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

    但是他们无法回答您的直接问题:“上述方法之间有什么区别?”,它处理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 类虽然已加载,但尚未初始化(即,它的&lt;clinit&gt; 尚未被调用)。其实,你甚至可以在初始化之前通过反射来查询类的成员!

    你为什么要关心?

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

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

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

    所以,这就是你问的两种方法的区别。

    【讨论】:

    • @Bruno,在你的句子上吹毛求疵,但我们通常这样做Class.forName("com.mysql.jdbc.Driver")。见stackoverflow.com/questions/8053095/…。无论如何,我想问的是有没有办法使用 ClassLoader 来初始化一个类?
    • @Pacarier,如今,我们在处理现代代码时通常不会这样做。仅在处理遗留代码时才(仍然)需要。是的,您可以使用Class.forName(String, true, ClassLoader) 初始化一个类。有关更多信息,请参阅 Class.forName 的 api 文档。
    【解决方案2】:

    快速解答(无代码示例)

    使用显式ClassLoader cls = &lt;a ClassLoader&gt;; 方法,您可以灵活地从一个不是默认ClassLoader 的ClassLoader 加载类。在您的情况下,您使用的是默认的 System ClassLoader,因此它给出了与 Class.forName(String name) 调用类似的总体结果(带有最终对象差异的实例化),但您可以引用另一个 ClassLoader .

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

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

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

    【讨论】:

    • 别忘了:由不同类加载器加载的类实例化的对象不是Equal(),即使它们的数据相同。即使它们是从同一个类创建的(由两个不同的类加载器加载)。这可能是极其难以发现的错误的根源。
    • 其实@TMN 是错误的。让cl1cl2 成为两个不同的类加载器,让A 成为一个用return true; 覆盖.equals(...) 的类(也就是说,它总是返回true)。然后cl1.loadClass("A").newInstance().equals(cl2.loadClass("B").newInstance()) 返回真。
    • “forName”是什么意思?这是一个奇怪的方法名称。为什么加载不出来?
    • 是的,那里的命名至少可以说有点奇怪:-)
    【解决方案3】:

    在你的具体情况下:

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

    上面的代码将加载TargetClass 总是system classloader

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

    第二个代码 sn-p 将加载(并初始化)TargetClass,其中 用于加载执行该代码行的类的类加载器。如果该类是使用系统类加载器加载的,那么这两种方法是相同的(除了类初始化,正如 Bruno 的出色回答中所解释的那样)。

    使用哪一个? 对于使用反射加载和检查类,我建议使用特定的类加载器 (ClassLoader.loadClass()) - 它让您可以控制并有助于避免不同类型之间潜在的模糊问题环境。

    如果您需要加载和初始化,请使用Class.forName(String, true, ClassLoader)

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

    • 如果您正在运行命令行应用程序,您可以只使用system classloader 或加载应用程序类的类加载器 (Class.getClassLoader())。
    • 如果您在托管环境(JavaEE、servlet 容器等)中运行,那么最好先检查current thread context class loader,然后再返回到前面给出的选项。李>
    • 或者只使用您自己的自定义类加载器(如果您喜欢这种东西)

    一般来说,最简单的和测试是使用来自 Spring 的 ClassUtils.forName()(参见 JavaDoc)。

    更深入的解释:


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

    来源:What is the difference between Class.forName() and ClassLoader.loadClass()?


    此外,SPR-2611 在使用 Class.forName(String, boolean, ClassLoader) 时突出显示了一个有趣的晦涩角落案例。

    从那个 Spring 问题中可以看出,使用 ClassLoader.loadClass() 是推荐的方法(当您需要从特定的类加载器加载类时)。

    【讨论】:

    • 这是一个优秀而全面的答案,应该被接受。
    • 这比接受的答案更完整。系统 vs 线程 vs 调用类加载器是一个棘手的概念。
    【解决方案4】:

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

    Class.forName() 可以在您不关心特定的类加载器并且想要与静态引用的类相同的类加载行为时使用。

    【讨论】:

      【解决方案5】:

      来自API doc

      调用该方法等价于:

        Class.forName(className, true, currentLoader)
      

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

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

      【讨论】:

      【解决方案6】:

      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
      

      【讨论】:

        【解决方案7】:

        第二种方法使用ClassLoader 加载一个类

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

        JavaDoc 是这么说的:

        forName(String name, boolean initialize, ClassLoader loader)
        

        指定的类加载器用于 加载类或接口。如果 参数加载器为空,类为 通过引导类加载 装载机。

        所以,第二个选项使用系统类加载器(本质上,它在第一个选项中所做的)。

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            【解决方案9】:

            但是静态初始化块只有在我们使用 class.forname("..."); 时才会执行

            我刚刚测试过。

            【讨论】:

              猜你喜欢
              • 2020-06-26
              • 2011-01-06
              • 1970-01-01
              • 1970-01-01
              • 2012-09-02
              • 2013-11-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多