1. 概要

メソッドは、所有するクラスの外部から呼び出されないようにJavaで private に作成されていますが、何らかの理由で呼び出す必要がある場合があります。

これを実現するには、Javaのアクセス制御を回避する必要があります。 これは、ライブラリの隅に到達したり、通常はプライベートのままであるはずのコードをテストしたりするのに役立つ場合があります。

この短いチュートリアルでは、メソッドの可視性に関係なく、メソッドの機能を検証する方法を見ていきます。 Java ReflectionAPIとSpringのReflectionTestUtilsの2つの異なるアプローチを検討します。

2. 私たちの制御不能な可視性

この例では、long配列で動作するユーティリティクラスLongArrayUtilを使用してみましょう。 このクラスには、2つのindexOfメソッドがあります。

public static int indexOf(long[] array, long target) {
    return indexOf(array, target, 0, array.length);
}

private static int indexOf(long[] array, long target, int start, int end) {
    for (int i = start; i < end; i++) {
        if (array[i] == target) {
            return i;
        }
    }
    return -1;
}

これらのメソッドの可視性を変更できないと仮定しますが、プライベートindexOfメソッドを呼び出します。

3. Java Reflection API

3.1. リフレクションを使った方法を見つける

コンパイラーは、クラスに表示されない関数を呼び出さないようにしますが、リフレクションを介して関数を呼び出すことができます。 まず、呼び出したい関数を説明するMethodオブジェクトにアクセスする必要があります。

Method indexOfMethod = LongArrayUtil.class.getDeclaredMethod(
  "indexOf", long[].class, long.class, int.class, int.class);

非プライベートメソッドにアクセスするには、getDeclaredMethodを使用する必要があります。 関数を持つ型(この場合は LongArrayUtil )で呼び出し、パラメーターの型を渡して正しいメソッドを識別します。

メソッドが存在しない場合、関数は失敗し、例外をスローする可能性があります。

3.2. メソッドへのアクセスを許可する

次に、メソッドの可視性を一時的に上げる必要があります。

indexOfMethod.setAccessible(true);

この変更は、JVMが停止するか、accessibleプロパティがfalseに戻されるまで続きます。

3.3. リフレクションを使用してメソッドを呼び出す

最後に、Methodオブジェクトに対してcallinvokeを呼び出します。

int value = (int) indexOfMethod.invoke(
  LongArrayUtil.class, someLongArray, 2L, 0, someLongArray.length);

これで、プライベートメソッドに正常にアクセスできました。

invoke の最初の引数はターゲットオブジェクトであり、残りの引数はメソッドのシグネチャと一致する必要があります。 この場合のように、メソッドは static であり、ターゲットオブジェクトは親クラスLongArrayUtilです。 インスタンスメソッドを呼び出すために、呼び出しているメソッドのオブジェクトを渡します。

また、invokeObjectを返すことに注意してください。これは、void関数のnullであり、適切なタイプにキャストする必要があります。それを使用します。

4. Spring ReflectionTestUtils

クラスの内部に到達することは、テストにおける一般的な問題です。 Springのテストライブラリは、単体テストがクラスに到達するのに役立ついくつかのショートカットを提供します。 これにより、Springが実行時にインスタンス化する可能性のあるプライベートフィールドにテストがアクセスする必要がある単体テストに固有の問題が解決されることがよくあります。

まず、pom.xmlにspring-test依存関係を追加する必要があります。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.4</version>
    <scope>test</scope>
</dependency>

これで、ReflectionTestUtilsinvokeMethod関数を使用できるようになりました。これは、上記と同じアルゴリズムを使用し、コードの記述を節約します。

int value = ReflectionTestUtils.invokeMethod(
  LongArrayUtil.class, "indexOf", someLongArray, 1L, 1, someLongArray.length);

これはテストライブラリであるため、テストコードの外部でこれを使用することは期待できません。

5. 考慮事項

リフレクションを使用して関数の可視性をバイパスすることにはいくつかのリスクが伴い、不可能な場合もあります。 考慮すべきこと:

  • JavaSecurityManagerがランタイムでこれを許可するかどうか
  • コンパイル時のチェックなしで呼び出している関数が、将来呼び出すために存在し続けるかどうか
  • 独自のコードをリファクタリングして、物事をより見やすく、アクセスしやすくします

6. 結論

この記事では、JavaReflectionAPIとSpringのReflectionTestUtilsを使用してプライベートメソッドにアクセスする方法について説明しました。

いつものように、この記事のサンプルコードは、GitHubにあります。