java getClass()&类加载器不同时的instanceof语义

qpgpyjmq  于 2023-03-11  发布在  Java
关注(0)|答案(1)|浏览(138)

似乎来自不同类加载器的“相同”类不应该出现在相同的执行上下文中,OSGI /应用服务器确保边界不被侵犯。
想象一下,我们从监狱里逃出来,正在比较对象,这些对象“相同”(数据、包和类名),但加载自不同的jar:方法的字节码可以不同,一个可以针对Java8编译,另一个针对Java 17编译。
我期望obj1 instanceof Clz2obj1.getClass() == obj2.getClass()返回false
类装入器会破坏相等性和继承性吗?

vd8tlhqk

vd8tlhqk1#

你试过了吗?你最终得到的结果确实是false。你可以这样写代码:

import com.foo.Foo;

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

并调用它,使其首先打印com.foo.Foo,然后仍然打印false,甚至导致有趣的错误消息“类型com.foo.foo的示例不能强制转换为com.foo.foo”。
问题是,这里有两个java.lang.Class的实际示例,名称都是com.foo.Foo,但'classLoader'的值不同,就java而言,这两个类在任何方面都不相关或不兼容
写作时:

foo instanceof Foo

涉及2种类型:

  • Foo被直接写入源文件,由JVM翻译成字节码,字节码(类文件)被读入并“解析”,这导致一个"这实际上是什么类“,它将与此代码所在的任何类具有相同的类加载器。
  • 变量foo为参考变量;它指向某个对象,该对象知道它的类型--该对象由某个new X()调用启动,并且“类型”的工作原理与上一条完全相同,但这次是针对X的,而且至关重要的是,这次是在new X()源代码表达式所在的类的上下文中。

如果ClassThatExecutedNewFooClassThatIsExecutingInstanceofFoo有不同的加载器 * 并且 * 它们最终没有将加载Foo的工作委托给一个共同的父类,那么最终会有两个不同的类,因此foo instanceof Foo会导致false,即使foo.getClass().getName().equals(Foo.class.getName())true。如果你愿意,JVM可以多次加载相同的类,只要涉及到多个类加载器。
父母
这里一个关键的方面是类加载器的层次结构。ClassLoader总是有一个父类,并且ClassLoader真的真的非常努力地让你编写新的类,这样它们 * 首先 * 要求它们的“父加载器”加载一个类,并且只有当父类不能完成任务时,然后并且只有在那时ClassLoader才会自己尝试。
一旦你想一想,原因应该是显而易见的:如果它不是这样工作的,并且每个类加载器都打算自己加载您要求的任何东西,那么这就意味着加载器A加载的类和加载器B加载的类甚至在java.lang.String这样完全基本的东西上都不一致。java.lang.Object。这使得这两个类的代码之间几乎不可能进行任何通信。必须有 * 一些 *共同点的形式让他们可以互相交谈。
因此,如果你只是简单地按照显而易见的路径编写class MyLoader extends ClassLoader,并实现findClass方法,你很可能会遇到这种共享父代的情况,你可以创建两个加载器,但仍然得出结论:奇怪的是,我让加载器A加载com.foo.Foo,然后让加载器B加载它,我希望最终得到两个完全不兼容的Foo类型(instanceof为假,诸如此类),但事实并非如此。
如果发生这种情况,那是因为你的两个加载器都要求parent加载它,并且 it 成功了,这意味着Foo的加载器实际上是那个parent,而不是A,也不是B。同一个类被同一个加载器加载?这确实是同一个类,因此它们 * 是 * 彼此兼容的。
你可以阻止这一切的发生要么确保父加载程序找不到它(在'parent loader'是java为你创建的主应用程序加载器的常见情况下,只需确保com.foo.Foo在类路径上是而不是!)-或者,覆盖loadClass,但是当你这样做的时候,要真实的小心。你不应该在一个加载器中defineClass()这个类,除非你想让这个类和其他加载器不兼容(当然除了你自己的加载器的子类)。例如,尝试加载java.lang.String将工作,但这意味着你加载的类中的字符串现在与其他任何地方的字符串不兼容,这不太可能是你想要的。

相关问题