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.class渡す<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 ブロックを削除して、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 はありません。私のクラスは他のクラスを使用していますが、それらのクラスはまったく使用<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 がまったく表示されません。jar 内のクラス ファイル内の正確なバイトである正しいバイトが MyClassLoader 内の byte[] に含まれる defineClass まで、アプリケーションをデバッグできました。それでは、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 をロードすると、すべての問題が修正されます。今、私の唯一の質問は、なぜですか?loadClass メソッドによって返されるクラスを使用できるようにするために、classname$1.class と classname.clss の両方をロードする必要があるのはなぜですか? 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 ファイルであると思います。現在ロードされているクラスの 1 つを除外すると、NoClassDefFoundError が発生します。私の質問は、なぜですか?

ありがとう!- ジョーイ

4

0 に答える 0