我知道有些问题可以解决同样的问题,请相信我,我已经通过它们,但没有一个包含我的问题的答案。我想从字节数组加载一个类并验证其内容。我有类的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
如果我使用<classname>$1.class
并将<packagename>.<classname>$1
传递给loadClass方法,我将获得在该类中运行的唯一方法。当我尝试使用我想要加载的实际类时,我会得到各种异常。我将提供有关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块并仅在catch块中保留loadClass:
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。我可以调试我的应用程序直到defineClass,其中MyClassLoader中的byte []包含正确的字节,这些字节恰好是我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相当简单。打印Class的CanonicalName,并在其下面声明稍微缩进的声明的Method名称。这是输出:
null
run
pooledtcpconnector.PooledTCPConnector
openConnection
setRequestHeaders
SendSynchronousRequest
Initialize
QueryWebservice
这确实是正确的,因为PooledTCPConnector具有在其中声明的那些确切的函数,并且我相信null是包含该run函数的$ 1.class文件。如果我省略了一个当前加载的类,我得到一个NoClassDefFoundError。我的问题是,为什么?
谢谢! - 乔伊