(注:このブログはもう更新していません)この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

[クラウド帳票エンジンDocurain]

java.lang.reflect.AccessibleObject#isAccessibleは常にfalseを返す

java.lang.reflect.AccessibleObject#setAccessibleは、
例えばprivateメソッドをリフレクションで無理矢理呼び出す場合に使うメソッド。
ちょっと込み入ったことをする場合に良く使います。


で、これと対になるisAccessibleなんですが。
名前を見る限り、「アクセス可能かどうか」を調べるメソッドに見えます。

僕は『そのままMethod#invokeなどを呼び出したとき、IllegalAccessExceptionにならないか否か』を
調べるメソッドだと思ってました。ずっと。でも、そうではありませんでした。

実験

下のコードを実行してみます。

public class Main {

    public void aMethod() {
        System.out.println("aMethod");
    }

    public static void main(String... args) throws Exception {
        System.out.println(Main.class.getMethod("aMethod").isAccessible());
        Main.class.getMethod("aMethod").invoke(new Main());
    }
}


実行結果は次の通り。

false
aMethod


…この結果を見る限り、aMethodのinvokeは成功しているけど、isAccessibleはfalseです。

Javadocによると

isAccessibleのJavadocにはこのように記述されてます。

Get the value of the accessible flag for this object.


このオブジェクトがアクセス可能かどうかのフラグ値を取得する。


…うーん、よくわかりません。


ただ、さっきの実験結果を鑑みるに、どうやら「アクセス可能かどうかのフラグ値」とは、
そのままinvoke(またはField#set/get)を呼び出したときにIllegalAccessExceptionがスローされるか否かを示すフラグではないらしい。


で、AccessibleObjectのソースには次のように書かれてます。

    /**
     * Get the value of the <tt>accessible</tt> flag for this object.
     *
     * @return the value of the object's <tt>accessible</tt> flag
     */
    public boolean isAccessible() {
	return override;
    }

    ...

    // Indicates whether language-level access checks are overridden
    // by this object. Initializes to "false". This field is used by
    // Field, Method, and Constructor.
    //
    // NOTE: for security purposes, this field must not be visible
    // outside this package.
    boolean override;

Indicates whether language-level access checks are overridden
by this object. Initializes to "false". This field is used by
Field, Method, and Constructor.

NOTE: for security purposes, this field must not be visible
outside this package.



言語レベルでのアクセスチェックが変更(オーバーライド)されているかどうかを表す。
初期値はfalseである。このフィールドはField, Method, Constructorクラスで使用する。


ノート:セキュリティ上の理由により、このフィールドはパッケージ外へ公開してはならない。


ふーん。なるほど。

こんなスレッドを発見

色々探したらこんなのを見つけました。

http://forums.sun.com/thread.jspa?threadID=5338572


で、ここによると

  • isAccessibleが示しているのは、ランタイム時のアクセスチェックをスキップするか否かのフラグだ。publicかどうかじゃない。
  • publicかどうかを調べたいのならgetModifiersを使え。

とのこと。さっきのAccessbleObjectのソースコメントと一致します。

だから何?

で、この事実を受けて何が変わるかというと、
例えば以下のようなコードの書き方が変わってくるわけです。

public class MethodUtil {

    public static Object invokeForce(Method m, Object target) throws Exception {
        if (!m.isAccessible())
            m.setAccessible(true); // 結果として常に実行されてしまう

        return m.invoke(target);
    }
}


これは、次のようにした方が良い。

public class MethodUtil {

    public static Object invokeForce(Method m, Object target) throws Exception {

        // publicメソッドでなく、setAccessible(true)をまだ呼び出していないなら実行
        if (!Modifier.isPublic(m.getModifiers()) && !m.isAccessible())
            m.setAccessible(true);

        return m.invoke(target);
    }
}


何でわざわざこんなことするかというと、単にパフォーマンスの問題。

どうやらsetAccessibleの実行コストは、それほど激安というわけでもなさそうな感じだからです。
(但しセキュリティマネージャ有効時)


下の抜粋コードを参照。

(java.lang.reflect.AccessbleObjectから抜粋)

    public void setAccessible(boolean flag) throws SecurityException {
	SecurityManager sm = System.getSecurityManager();
	if (sm != null) sm.checkPermission(ACCESS_PERMISSION); // ←ここが高コスト
	setAccessible0(this, flag);
    }

    /* Check that you aren't exposing java.lang.Class.<init>. */
    private static void setAccessible0(AccessibleObject obj, boolean flag)
	throws SecurityException
    {
	if (obj instanceof Constructor && flag == true) {
	    Constructor c = (Constructor)obj;
	    if (c.getDeclaringClass() == Class.class) {
		throw new SecurityException("Can not make a java.lang.Class" +
					    " constructor accessible");
	    }
	}
	obj.override = flag;
    }


ま、そんなに目くじら立てるほどではないよねー、と言えばそれまでなんですけど。

追記


あたりまえだけど、setAccessible(true)をした後は、isAccessibleの結果はtrue。


setAccessibleのおかげで、Method, Field, Constructorはステートフルなオブジェクトです。
しかもsynchronizedされてもいない。つーことは…


リフレクションの結果をキャッシュするのは、ちょっとしたパフォーマンス稼ぎのための常套句ですが、
ホントにやっていいのか考えてからにしとかないと、足元掬われるハメになるかも。

ま、殆どのケースでは問題ナシなんでしょうけどね。