Frage

Ich versuche, einen Event -Dispatcher zu verwenden, um ein Modell zu ermöglichen, abonnierte Zuhörer zu benachrichtigen, wenn es sich ändert. Der Event -Dispatcher erhält eine Handlerklasse und einen Methodennamen zum Anrufen während des Versands. Der Moderator zeichnet sich für die Modelländerungen ab und bietet eine Handler -Implementierung an, die Änderungen auffordert.

Hier ist der Code (es tut mir leid, dass es ein bisschen lang ist).

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());
            }
        }
    }
}

Modell:

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();
}

Moderator:

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");
            }
        });
    }
}

Hauptsächlich:

package main;

public class Main {
    public static void main(String[] args) {
        Model model = new Model();
        Presenter presenter = new Presenter(model);
        model.change();
    }
}

Jetzt erwarte ich, dass die Nachricht "Modell geändert" wird. Ich bekomme jedoch eine java.lang.Iillegalaccessexception: Klasse utils.eventDispatcher kann nicht auf ein Mitglied des Klassen -Moderators zugreifen.

Ich verstehe, dass die Schuld an der anonymen Klasse, die ich im Moderator erstellt habe, ist, aber ich weiß nicht, wie ich es "öffentlicher" machen soll als derzeit. Wenn ich es durch eine benannte verschachtelte Klasse ersetze, scheint es zu funktionieren. Es funktioniert auch, wenn der Moderator und der EventDispatcher im selben Paket sind, aber ich kann das nicht zulassen (mehrere Moderatoren in verschiedenen Paketen sollten den EventDispatcher verwenden)

irgendwelche Ideen?

War es hilfreich?

Lösung

Dies ist ein Fehler im JVM (Fehler 4819108)

Die Problemumgehung ist anzurufen method.setAccessible(true) vor dem Anruf an method.invoke(listener)

Andere Tipps

Ich vermute, dass eine anonyme Klasse immer ist private, aber ich habe in der Java -Sprachspezifikation keine klare Aussage dazu gefunden (ich habe in §15.9.5 gesucht)

In Java, wenn ein Typ nicht zugänglich ist, sind es seine Mitglieder auch nicht.

Wenn Sie schwarze Magie mögen, können Sie die Zugriffsüberprüfung mithilfe verwenden method.setAccessible(true). Alternativ, können Sie verlangen, dass Ihre Event -Handler als Klassen bezeichnet werden, oder die betreffende Methode, die in zugänglichen Typen deklariert wird.

Das Problem hier ist, dass Sie in dem Code, der Reflexion verwendet, die Klasse und nicht die Schnittstelle widerspiegeln.

Unter Nichtreflexionsumständen die listener würde nicht als vom Typ betrachtet werden presenter.Presenter$1. Sie würden es durch a verwenden ModelChangedHandler Hinweis. ModelChangedHandler ist ein öffentlicher Typ und hat eine öffentliche Methode, und dieser polymorphe Zugang wäre erlaubt.

Aber weil du benutzt getClass(), Sie erhalten die eigentliche Implementierungsklasse. Normalerweise ist diese Klasse überhaupt nicht zugänglich. Lokale und anonyme Klassen sind nicht auf höchstem Niveau und nicht von Mitgliedern. Daher ist "Zugang" für sie nicht definiert.

Tatsächlich ist der eigentliche Fehler hier die Tatsache, dass der Reflexionsmechanismus die "No Access -Modifikatoren" als "Standardzugriff" betrachtet, der "Paket privat" ist. Daher ermöglicht es diesen Vorgang, wenn sich die Typen im selben Paket befinden. IMO, es hätte berichten müssen IllegalAccessException Auch wenn sie sich im selben Paket befinden, da es keinen Zugriff auf die angegebene Klasse gibt, von der Sie sie anrufen, und die Zugriffsbeschränkung sollte ausdrücklich mitgehoben werden method.setAccessible(true).

Was wäre also die korrektere Art, dies zu tun? Sie sollten mit dem zugreifen Schnittstelle Class Objekt.

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());
            }
        }
    }
}

In dieser Version übergeben wir dem Konstruktor ein Klassenobjekt für die erforderliche Schnittstelle sowie den Methodennamen. Wir erstellen die Method Objekt im Konstruktor. Es ist ein Spiegelbild der Methode in der Schnittstelle selbst. Nicht die Klasse.

Im dispatch, Wenn wir die Methode aufrufen, wenden wir die an Schnittstelle Methode zum gegebenen Hörer. Dies ist die Reflexion in Kombination mit Polymorphismus.

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();
    }
}

Also hier in der Model, Wir verwenden das Klassenliteral der Schnittstelle - was wir wissen, weil wir hier entscheiden, welche Schnittstelle verwendet werden soll.

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();
        }
    }
}

Die einzige Änderung hier ist der Versuch.

Diesmal - keine Zugriffsprobleme. Die Methode wird polymorph aufgerufen und ist perfekt zugänglich!

Dies ist eine wirklich schlechte Idee, in diesem Fall Reflexion zu verwenden. Lassen Sie Ihren Dispatcher einfach die erforderliche Methode aufrufen. Wenn Sie mehrere Disponenten benötigen, um verschiedene Methoden aufzurufen, klassifizieren Sie sie einfach.

Java fehlt Schließungen, aber Hilfe ist unterwegs!

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top