Accédez à une exception lors de l'invoquer la méthode d'une classe anonyme à l'aide de la réflexion Java

StackOverflow https://stackoverflow.com/questions/2659517

Question

J'essaie d'utiliser un répartiteur d'événements pour permettre à un modèle de notifier les auditeurs souscrits lorsqu'il change. Le répartiteur d'événements reçoit une classe de gestionnaire et un nom de méthode à appeler pendant la répartition. Le présentateur s'abonne aux modifications du modèle et fournit une implémentation de gestionnaire à appeler les modifications.

Voici le code (je suis désolé que ce soit un peu long).

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

Modèle:

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

Présentateur:

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

Principal:

package main;

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

Maintenant, je m'attends à obtenir le message "modèle modifié". Cependant, je reçois un java.lang.ILLEGALACCESSException: class utils.eventdispatcher ne peut pas accéder à un membre de la classe présentateur.presenter 1 $ avec des modificateurs "public".

Je comprends que la classe à blâmer est la classe anonyme que j'ai créée à l'intérieur du présentateur, mais je ne sais pas comment le rendre plus «public» qu'elle ne l'est actuellement. Si je le remplace par une classe imbriquée nommée, cela semble fonctionner. Cela fonctionne également si le présentateur et le EventDispatcher sont dans le même package, mais je ne peux pas l'autoriser (plusieurs présentateurs dans différents packages devraient utiliser le EventDispatcher)

des idées?

Était-ce utile?

La solution

Ceci est un bug dans le JVM (Bug 4819108)

La solution de contournement est d'appeler method.setAccessible(true) Avant l'appel à method.invoke(listener)

Autres conseils

Je suppose qu'un cours anonyme est toujours private, mais je n'ai pas trouvé de déclaration claire à ce sujet dans la spécification de la langue java (j'ai regardé au §15.9.5)

En Java, si un type n'est pas accessible, ses membres ne sont pas non plus.

Si vous aimez la magie noire, vous pouvez désactiver la vérification d'accès method.setAccessible(true). Alternativement, vous pouvez exiger que vos gestionnaires d'événements soient nommés classes, ou la méthode en question déclarée en types accessibles.

Le problème ici est que dans le code qui utilise la réflexion, vous reflétez la classe plutôt que l'interface.

Dans des circonstances non réfléchissantes, le listener ne serait pas considéré comme de type presenter.Presenter$1. Vous l'utiliseriez via un ModelChangedHandler référence. ModelChangedHandler est un type public et il a une méthode publique, et cet accès polymorphe serait autorisé.

Mais parce que vous utilisez getClass(), vous obtenez la classe d'implémentation réelle. Normalement, cette classe n'est pas du tout accessible. Les classes locales et anonymes ne sont pas des cours de haut niveau et non de membres. Par conséquent, "l'accès" n'est pas défini pour eux.

En fait, le vrai bug ici est le fait que le mécanisme de réflexion considère les "modificateurs sans accès" comme "Accès par défaut" qui est "package privé". Il permet donc cette opération lorsque les types sont dans le même package. OMI, il aurait dû signaler IllegalAccessException Même lorsqu'ils sont dans le même package, car il n'y a pas d'accès à la classe donnée d'où vous l'appelez, et la restriction d'accès doit être explicitement levée method.setAccessible(true).

Alors, quelle serait la façon la plus correcte de faire cela? Vous devez y accéder en utilisant le interface Class objet.

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

Dans cette version, nous passons au constructeur un objet de classe pour l'interface requise, ainsi que le nom de la méthode. Nous créons le Method Objet dans le constructeur. C'est le reflet de la méthode dans le interface lui-même. Pas la classe.

Dans dispatch, lorsque nous invoquons la méthode, nous appliquons le interface Méthode à l'auditeur donné. C'est une réflexion combinée au polymorphisme.

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

Alors ici dans le Model, nous utilisons le littéral de classe de l'interface - que nous connaissons parce que c'est ici que nous décidons quelle interface utiliser.

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

Le seul changement ici est le coup d'essai.

Cette fois - pas de problèmes d'accès. La méthode est invoquée polymorphiquement et est parfaitement accessible!

C'est une très mauvaise idée d'utiliser la réflexion dans ce cas. Laissez simplement votre répartiteur appeler la méthode requise. Si vous avez besoin de plusieurs répartiteurs pour appeler différentes méthodes, il suffit de les sous-classer.

Java manque des fermetures mais l'aide est en route!

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top