1

我知道有一些问题可以解决同样的问题,相信我,我已经解决了这些问题,但没有一个问题包含我的问题的答案。我想从字节数组加载一个类并验证它的内容。我有该类的 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.classand 传递<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。我的问题是,为什么?

谢谢!- 乔伊

4

0 回答 0