5

我正在尝试使用事件调度程序来允许模型在更改时通知订阅的侦听器。事件分派器接收一个处理程序类和一个方法名称,以便在分派期间调用。演示者订阅模型更改并提供要在更改时调用的 Handler 实现。

这是代码(对不起,它有点长)。

事件分配器:

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

模型更改处理程序:

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 can't access a member of class presenter.Presenter$1 with modifiers "public"。

我知道要责备的类是我在演示者内部创建的匿名类,但是我不知道如何使它比现在更“公开”。如果我用一个命名的嵌套类替换它,它似乎可以工作。如果 Presenter 和 EventDispatcher 在同一个包中,它也可以工作,但我不能允许(不同包中的几个 Presenter 应该使用 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不会被认为是 type 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());
            }
        }
    }
}

在这个版本中,我们向构造函数传递了所需接口的 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 回答