5

イベントディスパッチャーを使用して、モデルが変更されたときにサブスクライブされたリスナーに通知できるようにしようとしています。イベントディスパッチャは、ディスパッチ中に呼び出すハンドラクラスとメソッド名を受け取ります。プレゼンターはモデルの変更をサブスクライブし、変更時に呼び出されるハンドラー実装を提供します。

これがコードです(少し長いのでごめんなさい)。

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が発生します。クラスutils.EventDispatcherは、修飾子「public」を使用してクラスpresenter.Presenter$1のメンバーにアクセスできません。

責任のあるクラスは、プレゼンター内で作成した匿名のクラスであることを理解していますが、現在よりも「公開」する方法がわかりません。名前付きのネストされたクラスに置き換えると、機能するようです。PresenterとEventDispatcherが同じパッケージに含まれている場合にも機能しますが、それを許可することはできません(異なるパッケージの複数のプレゼンターがEventDispatcherを使用する必要があります)

何か案は?

4

4 に答える 4

10

これはJVMのバグです(バグ4819108) 。

回避策は、への呼び出しmethod.setAccessible(true)の前に呼び出すことですmethod.invoke(listener)

于 2010-04-17T19:16:58.913 に答える
1

私の推測では、匿名クラスは常にですprivateが、Java言語仕様でこれに関する明確なステートメントを見つけられませんでした(§15.9.5を調べました)

Javaでは、型にアクセスできない場合、そのメンバーもアクセスできません。

ブラックマジックが好きな場合は、を使用してアクセスチェックを無効にすることができますmethod.setAccessible(true)。または、イベントハンドラーにクラスという名前を付けるか、問題のメソッドをアクセシブルな型で宣言するように要求することもできます。

于 2010-04-17T19:13:22.307 に答える
1

ここでの問題は、リフレクションを使用するコードでは、インターフェイスではなくクラスをリフレクトしていることです。

非反射の状況では、listenerはタイプとは見なされませんpresenter.Presenter$1。あなたは参照を通してそれを使用するでしょうModelChangedHandlerModelChangedHandlerはパブリックタイプであり、パブリックメソッドがあり、そのポリモーフィックアクセスが許可されます。

ただし、を使用し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());
            }
        }
    }
}

このバージョンでは、コンストラクターに必要なインターフェイスのClassオブジェクトと、メソッド名を渡します。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();
        }
    }
}

ここでの唯一の変更は、try-catchです。

今回は、アクセスの問題はありません。このメソッドは多形的に呼び出され、完全にアクセス可能です。

于 2015-11-23T17:54:06.190 に答える
0

その場合、これはリフレクションを使用するのは本当に悪い考えです。コーディネーターに必要なメソッドを呼び出させるだけです。異なるメソッドを呼び出すために複数のディスパッチャが必要な場合は、それらをサブクラス化するだけです。

Javaにはクロージャがありませんが、ヘルプは準備中です!

于 2010-04-17T19:12:53.503 に答える