リフレクションを使ってみよう プライベートメソッドアクセス編

More than 1 year has passed since last update.

WithOne AdventCalendar 13日目の記事です。

この記事では「java.lang.reflect」パッケージ。いわゆるリフレクションを紹介します。

今回は「prirvate」なメソッド、フィールドにアクセスする方法について書いていこうかと思います。
リフレクションを知っていれば簡単なことなのですが、「prirvate=外部から絶対アクセスできない。」
と思っている人も結構多いようです。

知っていると知らないとだとJunitテストとかの進み具合に影響でたりしますのでやり方をざっくり説明していきます。

とりあえず使ってみよう

下記は引数で受け取ったクラス、メソッドを実行するクラスです。

TestMain.java
public class TestMain {

    /**
     * メインクラス<br>
     * なんかするメイン
     * @param args 引数
     */
    public static void main(String[] args) {
        System.out.println("開始 TestMain");

        // 一番目の引数がクラス名
        String className = args[0];

        // 二番目の引数がメソッド名
        String methodName = args[1];

        try {

            // クラスを取得
            Class<?> c = Class.forName(className);
            System.out.println("実行クラス:" + c.getName());

            // インスタンスを作成
            Object o = c.newInstance();

            // メソッドを取得
            Method m = c.getMethod(methodName);
            System.out.println("実行メソッド:" + m.getName());

            // 実行
            m.invoke(o);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("終了 TestMain");
        }
    }
}

まぁフレームワークとかの内部を見るとよく書いてありそうな処理ですね。
プロパティファイルにクラス名とか書く場合は内部でこうゆう感じの処理を実行していると思ってもらえればOKです。

ではさっそく実行してみましょう。

image

引数に実行したいクラス、メソッドを設定して、実行・・・とすると

image

TestExecute.executeが実行されているのが分かります。

でもなんか障害発生してますね。TestExecuteを見てみましょう。

TestExecute.java
public class TestExecute {

    /** 実行フラグ */
    private static boolean FLG = true;

    /**
     * 実行メソッド
     */
    public void execute() {
        System.out.println("開始 TestExecute.execute");

        try {
            // どうがんばっても障害発生する
            if (FLG) {
                throw new Exception();
            }

            // 実行したい処理の呼び出し部分
            prirvateMethod();

        }catch (Exception e) {
            System.out.println("障害発生!");
        } finally {
            System.out.println("終了 TestExecute.execute");
        }
    }

    /**
     * プライベートなメソッド
     */
    private void prirvateMethod() {
        // 実行したい処理
        System.out.println("実行 TestExecute.prirvateMethod");
    }
}

プライベートなフィールド「FLG」が「true」なので絶対に障害発生しますね。
しかもコメント的に実際動かしたい処理は「prirvateMethod()」のようです。

そちらを実行してみましょう

image

実行!

image

「そんなメソッドないです。」って怒られましたね。まぁプライベートですからね。
通常、プライベートなメソッドは外部から見えませんのでアクセスできません。

「アクセスできない」という情報を書き換えてしまおう

しかし「java.lang.reflect.Method」はメソッドを制御するためのクラスですので当然アクセス制御も変更できます。
プライベートにアクセスするにはソースを下記のように変更します。

TestMain.java
public class TestMain {

    /**
     * メインクラス<br>
     * なんかするメイン
     *
     * @param args 引数
     */
    public static void main(String[] args) {
        System.out.println("開始 TestMain");

        // 一番目の引数がクラス名
        String className = args[0];

        // 二番目の引数がメソッド名
        String methodName = args[1];

        try {

            // クラスを取得
            Class<?> c = Class.forName(className);
            System.out.println("実行クラス:" + c.getName());

            // インスタンスを作成
            Object o = c.newInstance();

            // メソッドを取得
            Method m = c.getDeclaredMethod(methodName);
            System.out.println("実行メソッド:" + m.getName());

            // メソッドをアクセス可能に変更
            m.setAccessible(true);

            // 実行
            m.invoke(o);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("終了 TestMain");
        }
    }
}

「getMethod」が「getDeclaredMethod」に変更されて「m.setAccessible(true)」という行が追加されていますね。
Declaredは宣言されたって意味で、とりあえず書いてあれば使えようが使えまいが取得するぜ!っていうメソッドです。

取得は「getDeclaredMethod」で出来るのですが、
取得しただけでは実行はできず、下記のように「java.lang.IllegalAccessException」が発生します。

image

そこで「setAccessible」を使用して強制的にアクセス可能な状態に変更してあげます。

では実際に実行してみましょう。

image

「prirvateMethod」が実行されているのがわかりますね。

プライベートなフィールドも書き換えてみよう

でもやっぱり「execute」は実行してほしいのでフィールド「FLG」を書き換えて見ましょう。
フィールド「FLG」はプライベートなので通常はアクセスできませんが、メソッドと同じ要領でアクセス可能に変更できます。

TestMain.java
public class TestMain {

    /**
     * メインクラス<br>
     * なんかするメイン
     *
     * @param args
     *            引数
     */
    public static void main(String[] args) {
        System.out.println("開始 TestMain");

        // 一番目の引数がクラス名
        String className = args[0];

        // 二番目の引数がメソッド名
        String methodName = args[1];

        try {

            // クラスを取得
            Class<?> c = Class.forName(className);
            System.out.println("実行クラス:" + c.getName());

            // インスタンスを作成
            Object o = c.newInstance();

            // フィールド「FLG」を取得
            Field f = c.getDeclaredField("FLG");
            System.out.println("変更フィールド:" + f.getName());

            // アクセス
            f.setAccessible(true);

            // 値をセット
            f.set(o, false);

            // メソッドを取得
            Method m = c.getMethod(methodName);
            System.out.println("実行メソッド:" + m.getName());

            // 実行
            m.invoke(o);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("終了 TestMain");
        }
    }
}

ほいで実行

image

image

「execute」を実行しつつ「prirvateMethod」も実行できましたね。

今回のリフレクションに関する説明は以上です。参考になったでしょうか?