我知道有一些问题可以解决同样的问题,相信我,我已经解决了这些问题,但没有一个问题包含我的问题的答案。我想从字节数组加载一个类并验证它的内容。我有该类的 byte[] 但我无法让我的自定义类加载器实际加载它。为简单起见,我创建了以下函数来读取 jar 文件字节:
final String dotJar = "path/to/jar/file";
File file = new File(dotJar);
FileInputStream fis = new FileInputStream(file);
byte[] contents = new byte[(int) file.length()];
fis.read(contents, 0, contents.length);
InputStream fStream = new ByteArrayInputStream(contents);
很简单。从这里开始,我使用以下代码从此流中检索类文件:
JarInputStream jis = new JarInputStream(fStream);
JarEntry entry;
do {
entry = jis.getNextJarEntry();
if (entry == null) {
break;
}
if ("<packagename>/<classname>.class".equals(entry.getName())) {
byte[] pContents = new byte[(int) entry.getSize()];
jis.read(pContents, 0, pContents.length);
ClassLoader cl = ClassLoader.getSystemClassLoader();
MyClassLoader mcl = new MyClassLoader(cl, pContents);
Class<?> pConnector = mcl.loadClass("<packagename>.<classname>");
if (pConnector == null) {
System.out.println("Failed.");
break;
}
Method[] declaredMethods = pConnector.getDeclaredMethods();
for (Method _m : declaredMethods) {
System.out.println(_m.getName());
}
}
} while (true);
jar 文件中的结构如下所示:
<packagename>
<classname>$1.class
<classname>.class
如果我对 loadClass 方法使用<classname>$1.class
and 传递<packagename>.<classname>$1
,我将得到在运行的该类中定义的唯一方法。当我尝试使用要加载的实际类时,会出现各种异常。我将提供所有详细信息以及我的 ClassLoader“实现”的来源。
private class MyClassLoader extends ClassLoader {
private byte[] classContents;
public MyClassLoader(ClassLoader parent, final byte[] cls) {
super(parent);
classContents = new byte[cls.length];
classContents = Arrays.copyOf(cls, cls.length);
}
@Override
public Class<?> loadClass(String name) throws NoClassDefFoundError {
Class<?> cls;
try {
cls = super.findSystemClass(name);
} catch (final ClassNotFoundException ex) {
cls = defineClass(name, classContents, 0, classContents.length);
}
resolveClass(cls);
return cls;
}
}
如果我将 MyClassLoader 保持原样,我将得到以下 StackTrace:
Exception in thread "main" java.lang.NoClassDefFoundError: <packagename>/<classname>$1 (wrong name: <packagename>/<classname>)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at poolexecutor.ValidationExample$MyClassLoader.loadClass(ValidationExample.java:35)
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2688)
at java.lang.Class.getDeclaredMethods(Class.java:1962)
at poolexecutor.ValidationExample.main(ValidationExample.java:70)
Java Result: 1
如果我删除 try 块并仅将 loadClass 留在 catch 块中:
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at poolexecutor.ValidationExample$MyClassLoader.loadClass(ValidationExample.java:35)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at poolexecutor.ValidationExample$MyClassLoader.loadClass(ValidationExample.java:35)
at poolexecutor.ValidationExample.main(ValidationExample.java:65)
Java Result: 1
为了解决这个问题,我从 defineClass 调用中删除了 name 属性,这会导致另一个具有以下 StackTrace 的异常:
Exception in thread "main" java.lang.ClassCircularityError: <packagename>/<classname>
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at poolexecutor.ValidationExample$MyClassLoader.loadClass(ValidationExample.java:35)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at poolexecutor.ValidationExample$MyClassLoader.loadClass(ValidationExample.java:35)
at poolexecutor.ValidationExample.main(ValidationExample.java:65)
Java Result: 1
没有 ClassCircularityError-s。我的班级使用其他班级,但这些班级没有<classname>
以任何方式使用。没有从这些类导入到<packagename>.<classname>
. 这是我的 jar 类的组织方式的层次结构:
Source Packages
1) PooledTCPConnector.java (Uses 2, 3, 4, 5, 6, 7, 8)
pooledtcpconnector.utilities
2) BackgroundWorker.java (Uses: 8)
3) ConnectionCache.java (Uses: -)
4) HTTPResponse.java (Uses: -)
5) ILogger.java (Interface)
pooledtcpconnector.wrappers
6) HTTPHeadersWrapper.java (Uses: 7, 8)
7) RequestHeaders.java (Uses: -)
8) ResponseHeaders.java (Uses: -)
老实说,我在这里看不到任何 CircularReferences。我可以调试我的应用程序,直到在 myClassLoader 中的 byte[] 包含正确字节的 defineClass 之前,这些字节正是我的 jar 中的类文件中的字节。所以让我们假设 byte[] 是正确的,但我仍然无法让 defineClass 工作。
我究竟做错了什么?
@Edit:通过根据以下内容稍微修改 MyClassLoader :
private static class StreamLoader extends ClassLoader {
private byte[] classContents;
public StreamLoader(ClassLoader parent) {
super(parent);
}
public void setClass(final byte[] cls) {
classContents = new byte[cls.length];
classContents = Arrays.copyOf(cls, cls.length);
}
@Override
public Class<?> loadClass(String name) throws NoClassDefFoundError {
Class<?> cls;
try {
cls = super.findSystemClass(name);
} catch (final ClassNotFoundException ex) {
cls = defineClass(name, classContents, 0, classContents.length);
}
resolveClass(cls);
return cls;
}
}
首先加载 classname$1.class 然后 classname.class 修复所有问题。现在我唯一的问题是为什么?为什么我需要同时加载 classname$1.class 和 classname.clss 才能使用 loadClass 方法返回的 Class?我知道 classname$1.class 包含嵌套类,但事实是,我没有嵌套类,甚至没有匿名类。这是它的外观:
StreamLoader mcl = new StreamLoader(ClassLoader.getSystemClassLoader());
mcl.setClass(get0);
Class<?> cls0 = mcl.loadClass("pooledtcpconnector.PooledTCPConnector$1");
mcl.setClass(get1);
Class<?> cls1 = mcl.loadClass("pooledtcpconnector.PooledTCPConnector");
PrintDeclaredMethods(cls0);
PrintDeclaredMethods(cls1);
PrintDeclaredMethod 相当简单。打印类的 CanonicalName 并在其下稍微缩进其声明的方法名称。这是输出:
null
run
pooledtcpconnector.PooledTCPConnector
openConnection
setRequestHeaders
SendSynchronousRequest
Initialize
QueryWebservice
这确实是正确的,因为 PooledTCPConnector 在其中声明了那些确切的函数,并且我相信 null 是包含该运行函数的 $1.class 文件。如果我遗漏了当前加载的类之一,我会得到 NoClassDefFoundError。我的问题是,为什么?
谢谢!- 乔伊