Java Reflectionを使用して匿名クラスの方法を呼び出すときのアクセス例外
-
27-09-2019 - |
質問
イベントディスパッチャーを使用して、モデルが購読されたリスナーが変更されたときに通知できるようにしようとしています。イベントディスパッチャーは、ディスパッチ中に呼び出すハンドラークラスとメソッド名を受け取ります。プレゼンターはモデルの変更を購読し、変更について呼び出されるハンドラーの実装を提供します。
これがコードです(少し長いと申し訳ありません)。
eventDispacther:
package utils;
public class EventDispatcher<T> {
List<T> listeners;
private String methodName;
public EventDispatcher(String methodName) {
listeners = new ArrayList<T>();
this.methodName = methodName;
}
public void add(T listener) {
listeners.add(listener);
}
public void dispatch() {
for (T listener : listeners) {
try {
Method method = listener.getClass().getMethod(methodName);
method.invoke(listener);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
}
モデル:
package model;
public class Model {
private EventDispatcher<ModelChangedHandler> dispatcher;
public Model() {
dispatcher = new EventDispatcher<ModelChangedHandler>("modelChanged");
}
public void whenModelChange(ModelChangedHandler handler) {
dispatcher.add(handler);
}
public void change() {
dispatcher.dispatch();
}
}
ModelChangedHandler:
package model;
public interface ModelChangedHandler {
void modelChanged();
}
プレゼンター:
package presenter;
public class Presenter {
private final Model model;
public Presenter(Model model) {
this.model = model;
this.model.whenModelChange(new ModelChangedHandler() {
@Override
public void modelChanged() {
System.out.println("model changed");
}
});
}
}
主要:
package main;
public class Main {
public static void main(String[] args) {
Model model = new Model();
Presenter presenter = new Presenter(model);
model.change();
}
}
今、私は「モデル変更」メッセージを取得することを期待しています。ただし、java.lang.illegalaccessexception:class utils.eventdispatcherは、クラスプレゼンターのメンバーにアクセスできません。
非難するクラスは、プレゼンター内で作成した匿名のクラスであることを理解していますが、現在よりも「公開」する方法を知りません。名前付きネストされたクラスに置き換えると、機能しているようです。また、プレゼンターとEventDispatcherが同じパッケージに入っている場合にも機能しますが、それを許可することはできません(異なるパッケージのいくつかのプレゼンターはEventDispatcherを使用する必要があります)
何か案は?
解決
これはJVMのバグです(バグ4819108)
回避策は呼び出すことです method.setAccessible(true)
通話の前に method.invoke(listener)
他のヒント
私の推測では、匿名のクラスは常にそうです private
, 、しかし、私はJava言語仕様でこれについて明確な声明を見つけませんでした(§15.9.5を見ました)
Javaでは、タイプにアクセスできない場合、そのメンバーでもありません。
あなたがブラックマジックが好きなら、あなたは使用を使用してアクセスチェックを無効にすることができます method.setAccessible(true)
. 。代わりに、イベントハンドラーの名前のクラス、または問題の方法がアクセス可能なタイプで宣言されるように要求することができます。
ここでの問題は、反射を使用するコードでは、インターフェイスではなくクラスを反映していることです。
非反射状況下では、 listener
タイプのものとは見なされません presenter.Presenter$1
. 。あなたはそれを介してそれを使用します ModelChangedHandler
参照。 ModelChangedHandler
パブリックタイプであり、パブリック方法があり、その多型アクセスが許可されます。
しかし、あなたが使用しているからです getClass()
, 、実際の実装クラスを取得しています。通常、このクラスにはまったくアクセスできません。ローカルおよび匿名のクラスはトップレベルではなく、メンバークラスではありません。したがって、「アクセス」は彼らのために定義されていません。
実際、ここでの実際のバグは、反射メカニズムが「アクセスなし修飾子」を「パッケージプライベート」である「デフォルトアクセス」と見なしているという事実です。したがって、タイプが同じパッケージにあるときにこの操作を許可します。 IMO、報告すべきだった IllegalAccessException
彼らが同じパッケージにいる場合でも、あなたがそれを呼んでいる場所から与えられたクラスにアクセスできないため、アクセス制限は明示的に持ち上げられるべきです method.setAccessible(true)
.
それで、これを行うより正しい方法は何でしょうか?を使用してアクセスする必要があります インターフェース Class
物体。
package util;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class EventDispatcher<T> {
List<T> listeners;
Method method;
public EventDispatcher(Class<? extends T> cls, String methodName) throws NoSuchMethodException, SecurityException {
listeners = new ArrayList<T>();
this.method = cls.getMethod(methodName);
}
public void add(T listener) {
listeners.add(listener);
}
public void dispatch() {
for (T listener : listeners) {
try {
method.invoke(listener);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
}
このバージョンでは、コンストラクターに必要なインターフェイスのクラスオブジェクトとメソッド名を渡します。を作成します Method
コンストラクターのオブジェクト。これは、の方法の反映です インターフェース 自体。クラスではありません。
の dispatch
, 、メソッドを呼び出すとき、 インターフェイス 指定されたリスナーへの方法。これは、多型と組み合わされた反射です。
package model;
import util.EventDispatcher;
public class Model {
private EventDispatcher<ModelChangedHandler> dispatcher;
public Model() throws NoSuchMethodException, SecurityException {
dispatcher = new EventDispatcher<ModelChangedHandler>(ModelChangedHandler.class, "modelChanged");
}
public void whenModelChange(ModelChangedHandler handler) {
dispatcher.add(handler);
}
public void change() {
dispatcher.dispatch();
}
}
だからここに Model
, 、インターフェイスのクラスリテラルを使用します。これは、使用するインターフェイスを決定するのはここであるためです。
package main;
import model.Model;
import presenter.Presenter;
public class Main {
public static void main(String[] args) {
Model model;
try {
model = new Model();
Presenter presenter = new Presenter(model);
model.change();
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
}
}
ここでの唯一の変更は、トライキャッチです。
今回 - アクセスの問題はありません。この方法は多型で呼び出され、完全にアクセス可能です!
これは、その場合にリフレクションを使用するのは本当に悪い考えです。ディスパッチャーに必要な方法を呼び出してもらいます。異なる方法を呼び出すためにいくつかのディスパッチャが必要な場合は、それらをサブクラス化するだけです。
Javaには閉鎖がありませんが、助けが近づいています!