getClass() 和 instanceof 语义,当类加载器不同时

getClass() & instanceof semantic when classloaders are different

提问人:gavenkoa 提问时间:3/9/2023 更新时间:3/9/2023 访问量:74

问:

似乎来自不同类加载器的“相同”类不应该出现在相同的执行上下文中,OSGI /应用程序服务器确保边界不被违反。

我们逃脱了监狱,正在比较对象,这些对象是“相等的”(数据、包和类名),但从不同的 jar 加载:方法的字节码可能不同,一个可以编译为 Java 8,另一个可以编译为 Java 17。

我期待并返回。obj1 instanceof Clz2obj1.getClass() == obj2.getClass()false

类加载器会破坏相等和继承吗?

Java 继承类 加载器 相等

评论


答:

1赞 rzwitserloot 3/9/2023 #1

你试过了吗?你确实会得到假的。您可以编写如下代码:

import com.foo.Foo;

void test(Object o) {
  System.out.println(o.getClass());
  System.out.println(o instanceof Foo);
}

并调用它,以便它首先打印,然后仍然打印,甚至导致有趣的错误消息“com.foo.Foo 类型的实例无法转换为 com.foo.Foo”。com.foo.Foofalse

问题是,您将在这里有 2 个实际实例,它们都带有 名称 ,但“classLoader”的值不同,就 java 而言,这 2 个类在任何方面都不相关或不兼容java.lang.Classcom.foo.Foo

写作时:

foo instanceof Foo

涉及 2 种类型:

  • 由JVM直接写入源文件,转换为字节码,字节码(类文件)被读入并“解析”,这导致了对“这实际上是什么类”的一种理解,并且它将具有与此代码所在的任何类相同的类加载器。Foo

  • 该变量是参考变量;它指向某个对象。该对象知道它的类型是什么 - 该对象通过某种调用开始生命,并且“类型”与上一个项目符号中的工作原理完全相同,但这次是 ,而且至关重要的是,这次是在源代码表达式所在的类的上下文中。foonew X()Xnew X()

如果 和 具有不同的加载器,并且它们最终没有将加载工作委托给共同的父级,那么您最终会得到 2 个不同的类,因此,即使 是 也会导致 。是的,只要涉及多个类加载器,JVM 就可以多次加载相同的类。ClassThatExecutedNewFooClassThatIsExecutingInstanceofFooFoofoo instanceof Foofalsefoo.getClass().getName().equals(Foo.class.getName())true

父母

这里的一个关键方面是类加载器层次结构。ClassLoader 总是有一个父类,而类加载器真的非常努力地让你编写新的类,这样他们首先要求他们的“父加载器”加载一个类,只有当父类无法完成工作时,ClassLoader 才会尝试。

一旦你仔细想想,原因应该是显而易见的:如果它没有像那样工作,并且每个类加载器都旨在自己加载你要求的任何内容,那么这意味着加载器 A 加载的类和加载器 B 加载的类甚至不会在像 .或者更糟的是,.这使得在这两个类的代码之间进行任何通信的尝试几乎是不可能的。他们必须有某种形式的共同点才能相互交谈。java.lang.Stringjava.lang.Object

因此,如果你只是按照显而易见的路径编写并实现该方法,你很可能会遇到这种共享的亲子关系情况。您可以制作 2 个加载器,但得出的结论是:奇怪,我要求加载器 A 加载,然后我让加载器 B 加载它,我期望最终得到 2 种完全不兼容的类型(是错误的,诸如此类的事情)。然而,事实并非如此。class MyLoader extends ClassLoaderfindClasscom.foo.FooFooinstanceof

如果发生这种情况,那是因为你的两个加载器首先要求父级来编写它,并且它成功了,这意味着加载器实际上是那个父级,而不是 A,也不是 B。这确实是同一个类,因此事物是相互兼容的。Foo

您可以阻止这种情况发生;要么确保 parent-loader 找不到它(在“parent loader”是 Java 为您创建的主要应用程序加载器的常见情况下,只需确保不在类路径上!) - 或者,覆盖 ,但当你这样做时,要非常小心。你不应该在加载器中打开类,除非你希望该类与任何其他加载器的尝试不兼容(当然,除了你自己的加载器的子类)。例如,尝试加载会起作用,但意味着您加载的类中的字符串现在与其他任何地方的字符串不兼容,这不太可能是您想要的。com.foo.FooloadClassdefineClass()java.lang.String