Domanda

Sto cercando di utilizzare un dispatcher di eventi per consentire a un modello di avvisare gli ascoltatori sottoscritti quando cambia. L'evento Dispatcher riceve una classe di gestori e un nome metodo da chiamare durante la spedizione. Il presentatore si iscrive alle modifiche del modello e fornisce un'implementazione del gestore per essere chiamata alle modifiche.

Ecco il codice (mi dispiace che sia un po 'lungo).

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

Modello:

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

Presentatore:

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

Principale:

package main;

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

Ora, mi aspetto di ottenere il messaggio "Modello modificato". Tuttavia, sto ricevendo un java.lang.illegalaccessException: Class Utils.EventDispatcher non può accedere a un membro di Class Presenter.Presenter $ 1 con modificatori "pubblico".

Capisco che la classe da incolpare è la classe anonima che ho creato all'interno del presentatore, tuttavia non so come renderlo più "pubblico" di quanto non sia attualmente. Se lo sostituisco con una classe nidificata nominata sembra funzionare. Funziona anche se il presentatore e EventDispatcher sono nello stesso pacchetto, ma non posso permetterlo (diversi presentatori in pacchetti diversi dovrebbero utilizzare EventDispatcher)

qualche idea?

È stato utile?

Soluzione

Questo è un bug nel JVM (Bug 4819108)

La soluzione alternativa deve chiamare method.setAccessible(true) Prima della chiamata a method.invoke(listener)

Altri suggerimenti

La mia ipotesi è che una classe anonima sia sempre private, ma non ho trovato una chiara dichiarazione al riguardo nella specifica del linguaggio Java (ho guardato in §15.9.5)

In Java, se un tipo non è accessibile, né i suoi membri.

Se ti piace la magia nera, puoi disabilitare l'accesso al controllo utilizzando method.setAccessible(true). In alternativa, potresti richiedere che i gestori di eventi vengano nominati classi o il metodo in questione dichiarato in tipi accessibili.

Il problema qui è che nel codice che utilizza la riflessione, stai riflettendo la classe piuttosto che l'interfaccia.

In circostanze non riflessive, il listener non sarebbe considerato di tipo presenter.Presenter$1. Lo useresti tramite un ModelChangedHandler riferimento. ModelChangedHandler è un tipo pubblico e ha un metodo pubblico e sarebbe consentito l'accesso polimorfico.

Ma perché stai usando getClass(), stai ottenendo la classe di implementazione effettiva. Normalmente, questa classe non è affatto accessibile. Le lezioni locali e anonime non sono lezioni di alto livello e non per membri. Pertanto "l'accesso" non è definito per loro.

In effetti, il vero bug qui è il fatto che il meccanismo di riflessione vede i "non modificatori di accesso" come "accesso predefinito" che è "pacchetto privato". Quindi consente questa operazione quando i tipi sono nello stesso pacchetto. IMO, avrebbe dovuto segnalare IllegalAccessException Anche quando sono nello stesso pacchetto, in quanto non c'è accesso alla classe data da dove la chiami e la restrizione di accesso dovrebbe essere esplicitamente sollevata con method.setAccessible(true).

Quindi quale sarebbe il modo più corretto di farlo? Dovresti accedervi usando il interfaccia Class oggetto.

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 questa versione, passiamo al costruttore un oggetto di classe per l'interfaccia richiesta e il nome del metodo. Creiamo il Method oggetto nel costruttore. È un riflesso del metodo nel interfaccia si. Non la classe.

In dispatch, quando invochiamo il metodo, applichiamo il interfaccia Metodo all'ascoltatore dato. Questa è una riflessione combinata con il polimorfismo.

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

Quindi qui nel Model, usiamo le letterali della classe dell'interfaccia, cosa che sappiamo perché è qui che decidiamo quale interfaccia utilizzare.

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

L'unica modifica qui è il battito di prova.

Questa volta - nessun problema di accesso. Il metodo è invocato polimorfico ed è perfettamente accessibile!

Questa è davvero una cattiva idea usare il riflesso in quel caso. Lascia che il tuo dispatcher chiami il metodo richiesto. Se hai bisogno di diversi dispatcher per chiamare metodi diversi, basta sottoclassirli.

A Java mancano le chiusure ma l'aiuto è in arrivo!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top