2

質問
custom をClassLoader使用して、静的コンテキストから適切に利用されるクラスの実装を提供することは可能ですか?

背景
私は、静的クラスを使用して依存関係を結び付けることを推奨するフレームワークを使用しています。
それはこのように動作します..

public class MyClass {

    @ThisIsADependency
    private MyDependency myDependency;

    public void initialize() {
        FrameworkProvidedDependencyResolver.resolveDependencies(this);
    }

}

ご想像のとおり、これをテストするのは悪夢であり、JUnit からは不可能なアクティブなフレームワーク環境内から呼び出されない限りFrameworkProvidedDependencyResolver(実際の名前ではありません) をスローします。NullPointerException

私がやりたいClassLoaderことは、JUnit テストで使用できるカスタムを提供して、FrameworkProvidedDependencyResolverモックの依存関係などを結び付けるカスタムを提供することです。

わかりましたので、ユニットテストを次のように表示したいと思います。

@RunWith(MyTestRunner.class)
public class TestMyClass {

    @Test
    public void testInitialization() {
        MyClass myClass = new MyClass();
        myClass.initialize();
        // not much of a test, I know
    }

}

MyTestRunnerカスタムを使用することを選択した場所ClassLoaderです..

public class MyTestRunner extends BlockJUnit4ClassRunner {

    public MyTestRunner(Class<?> clazz) throws InitializationError {
        super(getFromMyClassLoader(clazz));
    }

    private static Class<?> getFromMyClassLoader(Class<?> clazz) throws InitializationError {
        try {
            ClassLoader testClassLoader = new MyClassLoader();
            return Class.forName(clazz.getName(), true, testClassLoader);
        } catch (ClassNotFoundException e) {
            throw new InitializationError(e);
        }
    }

}

@AutomatedMikeに感謝します。

わかりましたので、テストのためにカスタムの依存関係リゾルバーとMyClassLoader交換する機会を得ることができるミックスに滑り込みます..FrameworkProvidedDependencyResolver

public class ZKTestClassLoader extends URLClassLoader {

    public ZKTestClassLoader() {
        super(((URLClassLoader) getSystemClassLoader()).getURLs());
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> loadedClass = findLoadedClass(name);
        if (loadedClass != null) {
            return loadedClass;
        }
        System.out.println("Loading " + name);
        if (name.startsWith("my.test.classes")) {
            // Make sure we use MyClassLoader to load the test classes,
            // thus any classes it loads (eg: MyClass) will come through here.
            return super.findClass(name);
        } else if (name.endsWith("FrameworkProvidedDependencyResolver")) {
            // What should do we do here?
        }
        return super.loadClass(name);
    }

}

これで、フレームワークによって提供されるものではなく、カスタムをロードできるようFrameworkProvidedDependencyResolverになりました..しかし、どうすればそれを行うことができますか?

「FrameworkProvidedDependencyResolver」のリクエストを無視して、別のクラス、たとえば「MyMockFrameworkProvidedDependencyResolver」を返すことができます。それは問題ありませんが、静的コンテキストから をMyClass.initialize呼び出すと、 . 理にかなっています。FrameworkProvidedDependencyResolverNoClassDefFoundError

MyMockFrameworkProvidedDependencyResolver本物と同じ名前FrameworkProvidedDependencyResolverを付けて、別のパッケージに入れることができます(例:i.hate.my.framework.FrameworkProvidedDependencyResolver)。MyClassは実際FrameworkProvidedDependencyResolverのパッケージ、およびすべてを具体的に見ているため、これも機能しません。

クラスに実際の名前を付けてFrameworkProvidedDependencyResolver、フレームワークが提供するものと同じパッケージに入れることができます..しかし、今ではClassLoader. JVMは2つによって混同され、クラスパスによって適切な方がロードされます。おそらく私のものです。ここでの問題は、これがすべてのテストに適用されるようになったことです。私が探している解決策ではありません。

最後に、 は ではないため使用できProxyませFrameworkProvidedDependencyResolverinterface

わかりました、私の質問をもう一度言います:静的コンテキストから適切に利用される
custom を使用して、クラスの実装を提供することは可能ですか? ClassLoaderおそらく、一意の名前を持つ独自の一意のパスにクラスを作成し、それをロードするときに編集して、上書きしようとしている予想されるパスと名前で JVM に表示されるようにすることはできますか? もちろん、他のソリューションも大歓迎です。

4

2 に答える 2

1

クラスローダーで物事を整理しようとしているときにあなたの質問を見つけました。あなたのコードは、どこが間違っているかを理解するのに役立ちました。次に、あなたが求めていると思われるものを達成するためにあなたのコードをいじりました。そして、Peter Nederwieser はすべての点で正しく、モッキング フレームワーク (PowerMock、JMockit) を使用することが最善の方法ですが、完全を期すために、これが私のloadClassために機能する私のバージョンです。

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
    Class<?> loadedClass = findLoadedClass(name);
    if (loadedClass != null) {
        return loadedClass;
    }
    System.out.println("Loading " + name);
    if (name.endsWith("FrameworkProvidedDependencyResolver")) {
        try {
            InputStream is =
                super
                    .getResourceAsStream("FrameworkProvidedDependencyResolver.class");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            for (int i = is.read(); i != -1; i = is.read()) {
                baos.write(i);
            }
            byte[] buf = baos.toByteArray();
            return defineClass(name, buf, 0, buf.length);
        } catch (Exception e) {
            throw new ClassNotFoundException("", e);
        }
    } else if (name.startsWith("my.test.classes")) {
        // Make sure we use MyClassLoader to load the test classes,
        // thus any classes it loads (eg: MyClass) will come through here.
        return super.findClass(name);
    }
    return super.loadClass(name);
}

FrameworkProvidedDependencyResolver.classコンパイルされた変更されたクラスです。元の と同じパッケージと名前を持つ必要があるFrameworkProvidedDependencyResolverため、元の と同じプロジェクトに共存させるのは少し難しい場合がFrameworkProvidedDependencyResolverあります。少なくとも IDE は満足しません。クラスを作成して編集し、コンパイルされたクラスファイルをIDEビルドフォルダーから取得して、クラスルートに配置し(super.getResourceAsStream("FrameworkProvidedDependencyResolver.class")パスなしで実行しているのはそのためです)、Javaファイルの名前を別のものに変更しました。実際の環境では、バイトコードを取得/保存する方法はおそらく異なるでしょう (試してみる価値はありません)。

2 つの条件分岐を並べ替えた理由は、すべてが同じパッケージに含まれていたからです。おそらく、あなたのケースではありません。

于 2015-06-11T15:46:46.500 に答える