Java 在不同的类加载器下从子类转换为超类
Java cast from subclass to superclass under different classloader
我知道 Class
由不同 class 加载程序加载的实例不能相互转换。
但是如果一个 Class
扩展另一个呢?我做了一个实验,结果令人困惑。这是我定义的ClassLoader
:
public class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
if (name.startsWith("java")) {
return super.loadClass(name);
}
String filename = "/" + name.replaceAll("\.", "/") + ".class";
InputStream is = getClass().getResourceAsStream(filename);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (Throwable e) {
throw new ClassNotFoundException(name);
}
}
}
以及实验代码:
// These classes will be loaded by MyClassLoader
class Parent { }
class Child extends Parent { }
class MyCalendarData_aa_DJ extends CalendarData_aa_DJ { }
class MyAppleScriptEngine extends AppleScriptEngine { }
class MyBufferedReader extends BufferedReader {
public MyBufferedReader(Reader in) {
super(in);
}
}
public class DifferentClassLoaderCast {
public static void main(String[] args) throws Exception {
ClassLoader classLoader = new MyClassLoader();
Class<?> pClass = classLoader.loadClass(Parent.class.getName());
Class<?> cClass = classLoader.loadClass(Child.class.getName());
// true, as pClass and cClass are loaded by same classloader
System.out.println(pClass.isAssignableFrom(cClass));
// false, different classloader
System.out.println(Parent.class.isAssignableFrom(cClass));
// true, why?
System.out.println(Object.class.isAssignableFrom(pClass));
Class<?> myCalendarData_aa_DJClass = classLoader.loadClass(MyCalendarData_aa_DJ.class.getName());
// false, CalendarData_aa_DJ is loaded by JAVA ext-classloader
System.out.println(CalendarData_aa_DJ.class.isAssignableFrom(myCalendarData_aa_DJClass));
Class<?> myAppleScriptEngine = classLoader.loadClass(MyAppleScriptEngine.class.getName());
// false, why? AppleScriptEngine is loaded by JAVA bootstrap-classloader
System.out.println(AppleScriptEngine.class.isAssignableFrom(myAppleScriptEngine));
Class<?> myBufferedReader = classLoader.loadClass(MyBufferedReader.class.getName());
// true, why? BufferedReader is loaded by JAVA bootstrap-classlaoder
System.out.println(BufferedReader.class.isAssignableFrom(myBufferedReader));
}
}
看来MyClassLoader
加载的subclass可以转换为bootstrap加载的superclass class loader under package starts with java
或内置 class?
// true, why?
System.out.println(Object.class.isAssignableFrom(pClass));
这一点应该是显而易见的。 Object 是 java.lang.Object
,如果完全限定名称以 java 开头,您会笨拙地调用 super.loadClass
。这意味着 Object.class 的加载程序是系统加载程序,并且对于所有加载操作都是如此:无论是 classLoader 加载 Parent 还是系统加载程序,它们都依赖于j.l.Object.class 由系统加载程序加载的概念:相同类型,因此兼容。
// false, why? AppleScriptEngine is loaded by JAVA bootstrap-classloader
System.out.println(AppleScriptEngine.class.isAssignableFrom(myAppleScriptEngine));
同理。相反:AppleScriptEngine
的完全限定名称是 而不是 以“java”开头。
Class<?> myBufferedReader = classLoader.loadClass(MyBufferedReader.class.getName());
// true, why? BufferedReader is loaded by JAVA bootstrap-classlaoder
System.out.println(BufferedReader.class.isAssignableFrom(myBufferedReader));
你猜对了。因为BufferedReader的FQN以“java”开头。
您可能误解了class加载模型。
class加载程序采用的模型是 parent/child 关系。一个 classloader 有一个 parent.
任何 class 都由某些 classloader 加载;如果它在其源代码中遇到任何其他 class,它将请求自己的 classloader 加载它。 但是该加载程序可能会将作业推迟到任何其他加载程序。这很重要。您的代码将延迟 FQN 以“java”开头的任何 class(甚至不是“java.”,这是一个特殊的选择)。否则,它会自行加载。作为 class 的 THE 加载程序记录的 class 加载程序是调用 defineClass
的加载程序。在您的代码中,如果您通过检查是否以“java”开头的 if 块,您的加载程序会 NOT 调用 defineClass,因此不是加载程序。如果那个 if
没有被采用,你总是最终会调用 defineClass,让你成为加载程序。
class加载器的常见模型是这样的:
请您的 parent(s) 按顺序加载 class。如果可以,太好了。我们 return 结果 这意味着 class 的加载程序是 parent 而不是你!
如果没有,那么这个加载器会加载它。不太可能发生冲突;毕竟,系统加载器甚至找不到它。现在你是装载机。
ClassLoader 本身支持此模型,但您可以通过覆盖 findClass
和 NOT loadClass
来获得它。 loadClass
的默认实现将完全按照上面的方式执行:首先调用 parents 的 loadClass 方法,只有当找不到它时,它才会调用 findClass 来完成工作。
我强烈建议您遵循此流程,并更新您的代码以扩展 findClass,而不是 loadClass。
如果您真的想自己加载它而不是委托给您的 parent 加载程序,那么,是的,重写 loadClass 就是您的做法。但是现在你必须处理这样一个事实,如果它是你的 parent 也可以找到的 class,那么你可以 运行 进入你的加载器加载的场景,比如说,com.foo.Example
和 parent 也是如此,虽然这些 class 具有完全相同的名称,但就 JVM 而言,它们完全无关并且彼此完全不兼容。 JVM 不介意,但它会导致高度混乱的场景,其中类型 com.foo.Example
的 object 不能分配给类型的变量... com.foo.Example
.
如果您必须这样做,请注意检查它是否以“java”开头是非常次优的。对于初学者,“java”。更合适,并且在几秒钟内,并非所有系统 classes 都以“java”开头。首先询问系统加载程序,如果它可以加载它,请至少遵从它(只是 return 它找到的内容)。
你想通过编写加载程序来完成什么?有了这种洞察力,我可以就哪种方法(loadClass 或 findClass)适合覆盖提供更多建议。
我知道 Class
由不同 class 加载程序加载的实例不能相互转换。
但是如果一个 Class
扩展另一个呢?我做了一个实验,结果令人困惑。这是我定义的ClassLoader
:
public class MyClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
if (name.startsWith("java")) {
return super.loadClass(name);
}
String filename = "/" + name.replaceAll("\.", "/") + ".class";
InputStream is = getClass().getResourceAsStream(filename);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (Throwable e) {
throw new ClassNotFoundException(name);
}
}
}
以及实验代码:
// These classes will be loaded by MyClassLoader
class Parent { }
class Child extends Parent { }
class MyCalendarData_aa_DJ extends CalendarData_aa_DJ { }
class MyAppleScriptEngine extends AppleScriptEngine { }
class MyBufferedReader extends BufferedReader {
public MyBufferedReader(Reader in) {
super(in);
}
}
public class DifferentClassLoaderCast {
public static void main(String[] args) throws Exception {
ClassLoader classLoader = new MyClassLoader();
Class<?> pClass = classLoader.loadClass(Parent.class.getName());
Class<?> cClass = classLoader.loadClass(Child.class.getName());
// true, as pClass and cClass are loaded by same classloader
System.out.println(pClass.isAssignableFrom(cClass));
// false, different classloader
System.out.println(Parent.class.isAssignableFrom(cClass));
// true, why?
System.out.println(Object.class.isAssignableFrom(pClass));
Class<?> myCalendarData_aa_DJClass = classLoader.loadClass(MyCalendarData_aa_DJ.class.getName());
// false, CalendarData_aa_DJ is loaded by JAVA ext-classloader
System.out.println(CalendarData_aa_DJ.class.isAssignableFrom(myCalendarData_aa_DJClass));
Class<?> myAppleScriptEngine = classLoader.loadClass(MyAppleScriptEngine.class.getName());
// false, why? AppleScriptEngine is loaded by JAVA bootstrap-classloader
System.out.println(AppleScriptEngine.class.isAssignableFrom(myAppleScriptEngine));
Class<?> myBufferedReader = classLoader.loadClass(MyBufferedReader.class.getName());
// true, why? BufferedReader is loaded by JAVA bootstrap-classlaoder
System.out.println(BufferedReader.class.isAssignableFrom(myBufferedReader));
}
}
看来MyClassLoader
加载的subclass可以转换为bootstrap加载的superclass class loader under package starts with java
或内置 class?
// true, why?
System.out.println(Object.class.isAssignableFrom(pClass));
这一点应该是显而易见的。 Object 是 java.lang.Object
,如果完全限定名称以 java 开头,您会笨拙地调用 super.loadClass
。这意味着 Object.class 的加载程序是系统加载程序,并且对于所有加载操作都是如此:无论是 classLoader 加载 Parent 还是系统加载程序,它们都依赖于j.l.Object.class 由系统加载程序加载的概念:相同类型,因此兼容。
// false, why? AppleScriptEngine is loaded by JAVA bootstrap-classloader
System.out.println(AppleScriptEngine.class.isAssignableFrom(myAppleScriptEngine));
同理。相反:AppleScriptEngine
的完全限定名称是 而不是 以“java”开头。
Class<?> myBufferedReader = classLoader.loadClass(MyBufferedReader.class.getName());
// true, why? BufferedReader is loaded by JAVA bootstrap-classlaoder
System.out.println(BufferedReader.class.isAssignableFrom(myBufferedReader));
你猜对了。因为BufferedReader的FQN以“java”开头。
您可能误解了class加载模型。
class加载程序采用的模型是 parent/child 关系。一个 classloader 有一个 parent.
任何 class 都由某些 classloader 加载;如果它在其源代码中遇到任何其他 class,它将请求自己的 classloader 加载它。 但是该加载程序可能会将作业推迟到任何其他加载程序。这很重要。您的代码将延迟 FQN 以“java”开头的任何 class(甚至不是“java.”,这是一个特殊的选择)。否则,它会自行加载。作为 class 的 THE 加载程序记录的 class 加载程序是调用 defineClass
的加载程序。在您的代码中,如果您通过检查是否以“java”开头的 if 块,您的加载程序会 NOT 调用 defineClass,因此不是加载程序。如果那个 if
没有被采用,你总是最终会调用 defineClass,让你成为加载程序。
class加载器的常见模型是这样的:
请您的 parent(s) 按顺序加载 class。如果可以,太好了。我们 return 结果 这意味着 class 的加载程序是 parent 而不是你!
如果没有,那么这个加载器会加载它。不太可能发生冲突;毕竟,系统加载器甚至找不到它。现在你是装载机。
ClassLoader 本身支持此模型,但您可以通过覆盖 findClass
和 NOT loadClass
来获得它。 loadClass
的默认实现将完全按照上面的方式执行:首先调用 parents 的 loadClass 方法,只有当找不到它时,它才会调用 findClass 来完成工作。
我强烈建议您遵循此流程,并更新您的代码以扩展 findClass,而不是 loadClass。
如果您真的想自己加载它而不是委托给您的 parent 加载程序,那么,是的,重写 loadClass 就是您的做法。但是现在你必须处理这样一个事实,如果它是你的 parent 也可以找到的 class,那么你可以 运行 进入你的加载器加载的场景,比如说,com.foo.Example
和 parent 也是如此,虽然这些 class 具有完全相同的名称,但就 JVM 而言,它们完全无关并且彼此完全不兼容。 JVM 不介意,但它会导致高度混乱的场景,其中类型 com.foo.Example
的 object 不能分配给类型的变量... com.foo.Example
.
如果您必须这样做,请注意检查它是否以“java”开头是非常次优的。对于初学者,“java”。更合适,并且在几秒钟内,并非所有系统 classes 都以“java”开头。首先询问系统加载程序,如果它可以加载它,请至少遵从它(只是 return 它找到的内容)。
你想通过编写加载程序来完成什么?有了这种洞察力,我可以就哪种方法(loadClass 或 findClass)适合覆盖提供更多建议。