使用java反射调用匿名类的方法时出现访问异常

发布于 2024-08-29 02:48:24 字数 2351 浏览 8 评论 0原文

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

这是代码(抱歉有点长)。

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

Model:

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

Presenter:

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

Main:

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 无法使用修饰符“public”访问类 Presenter.Presenter$1 的成员。

我知道应该归咎于我在演示者内部创建的匿名类,但是我不知道如何使其比目前更加“公开”。如果我用命名的嵌套类替换它,它似乎可以工作。如果 Presenter 和 EventDispatcher 位于同一个包中,它也可以工作,但我不能允许(不同包中的多个 Presenter 应该使用 EventDispatcher)

任何想法?

I'm trying to use an event dispatcher to allow a model to notify subscribed listeners when it changes. the event dispatcher receives a handler class and a method name to call during dispatch. the presenter subscribes to the model changes and provide a Handler implementation to be called on changes.

Here's the code (I'm sorry it's a bit 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());
            }
        }
    }
}

Model:

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

Presenter:

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

Main:

package main;

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

Now, I expect to get the "model changed" message. However, I'm getting an java.lang.IllegalAccessException: Class utils.EventDispatcher can not access a member of class presenter.Presenter$1 with modifiers "public".

I understand that the class to blame is the anonymous class i created inside the presenter, however I don't know how to make it any more 'public' than it currently is. If i replace it with a named nested class it seem to work. It also works if the Presenter and the EventDispatcher are in the same package, but I can't allow that (several presenters in different packages should use the EventDispatcher)

any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

顾北清歌寒 2024-09-05 02:48:24

这是 JVM 中的一个错误(bug 4819108),

解决方法是调用method.setAccessible(true) 在调用 method.invoke(listener) 之前

This is a bug in the JVM (bug 4819108)

The workaround is to call method.setAccessible(true) before the call to method.invoke(listener)

空袭的梦i 2024-09-05 02:48:24

我的猜测是,匿名类始终是 private,但我在 Java 语言规范中没有找到关于这一点的明确声明(我查看了第 15.9.5 节)

在 Java 中,如果类型是无法访问,其成员也无法访问。

如果您喜欢黑魔法,可以使用 method.setAccessible(true) 禁用访问检查。或者,您可能要求将事件处理程序命名为类,或者以可访问类型声明相关方法。

My guess is that an anonymous class is always private, but I didn't find a clear statement about this in the Java Language Specification (I looked in §15.9.5)

In Java, if a type is not accessible, neither are its members.

If you like black magic, you can disable access checking using method.setAccessible(true). Alternativly, you could require your event handlers to be named classes, or the method in question being declared in accessible types.

长途伴 2024-09-05 02:48:24

这里的问题是,在使用反射的代码中,您反射的是类而不是接口。

在非反射情况下,listener 不会被视为 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();
    }
}

因此,在模型中,我们使用接口的类文字 - 我们知道它,因为我们在这里决定使用哪个接口。

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。

这次 - 没有访问问题。该方法以多态方式调用,并且完全可以访问!

The problem here is that in the code that uses reflection, you are reflecting the class rather than the interface.

Under non-reflection circumstances, the listener would not be considered to be of type presenter.Presenter$1. You would be using it through a ModelChangedHandler reference. ModelChangedHandler is a public type and it has a public method, and that polymorphic access would be allowed.

But because you are using getClass(), you are getting the actual implementing class. Normally, this class is not accessible at all. Local and anonymous classes are not top-level and not member classes. Therefore "access" is not defined for them.

In fact, the real bug here is the fact that the reflection mechanism views the "no access modifiers" as "default access" which is "package private". So it allows this operation when the types are in the same package. IMO, it should have reported IllegalAccessException even when they are in the same package, as there is no access to the given class from where you are calling it, and the access restriction should explicitly be lifted with method.setAccessible(true).

So what would be the more correct way of doing this? You should access it using the interface Class object.

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 this version, we pass the constructor a Class object for the required interface, as well as the method name. We create the Method object in the constructor. It is a reflection of the method in the interface itself. Not the class.

In dispatch, when we invoke the method, we apply the interface's method to the given listener. This is reflection combined with polymorphism.

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

So here in the Model, we use the interface's class literal - which we know because it is here that we decide which interface to use.

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

The only change here is the try-catch.

This time - no access problems. The method is invoked polymorphically, and is perfectly accessible!

离去的眼神 2024-09-05 02:48:24

在这种情况下使用反射是一个非常糟糕的主意。只需让您的调度程序调用所需的方法即可。如果您需要多个调度程序来调用不同的方法,只需对它们进行子类化即可。

Java 缺少闭包,但帮助即将到来!

This is a really bad idea to use reflection in that case. Just let your dispatcher call the required method. If you need several dispatchers to call different methods, just subclass them.

Java is missing closures but help is on the way!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文