正确加载jar的字节数组中的类

时间:2014-09-17 09:54:58

标签: java jar dynamic-class-loaders

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

谢谢!   - 乔伊

0 个答案:

没有答案