簡體   English   中英

使用java反射調用匿名類的方法時訪問異常

[英]access exception when invoking method of an anonymous class using java reflection

我正在嘗試使用事件調度程序來允許模型在更改時通知已訂閱的偵聽器。 事件調度程序接收在調度期間調用的處理程序類和方法名稱。 演示者訂閱模型更改並提供要在更改時調用的Handler實現。

這是代碼(對不起,它有點長)。

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

模型:

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

主持人:

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:類utils.EventDispatcher無法使用修飾符“public”訪問類presenter.Presenter $ 1的成員。

我理解,應該責備的是我在演示者中創建的匿名課程,但是我不知道如何讓它比現在更加“公開”。 如果我用命名的嵌套類替換它,它似乎工作。 如果Presenter和EventDispatcher在同一個包中,它也可以工作,但我不能允許(不同包中的幾個演示者應該使用EventDispatcher)

有任何想法嗎?

這是JVM中的一個錯誤( bug 4819108

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

我的猜測是匿名類總是private ,但是我沒有在Java語言規范中找到關於這個的明確聲明(我查看了§15.9.5)

在Java中,如果某個類型不可訪問,則其成員也不可訪問。

如果您喜歡黑魔法,可以使用method.setAccessible(true)禁用訪問檢查。 或者,您可以要求將事件處理程序命名為類,或者以可訪問類型聲明有問題的方法。

這里的問題是,在使用反射的代碼中,您反映的是類而不是接口。

在不反思的情況下, 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();
    }
}

所以在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。

這一次 - 沒有訪問問題。 該方法以多態方式調用,並且完全可訪問!

在這種情況下使用反射是一個非常糟糕的主意。 只需讓您的調度員調用所需的方法即可。 如果您需要多個調度程序來調用不同的方法,只需將它們子類化。

Java缺少閉包,但幫助正在進行中!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM