Исключение доступа при вызове метода анонимного класса с использованием отражения Java

Я пытаюсь использовать диспетчер событий, чтобы позволить модели уведомлять подписанных слушателей, когда она изменяется. диспетчер событий получает класс обработчика и имя метода для вызова во время диспетчеризации. докладчик подписывается на изменения модели и предоставляет реализацию обработчика для вызова изменений.

Вот код (извините, он немного длинный).

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 не может получить доступ к члену класса Presenter.Presenter$1 с модификаторами "public".

Я понимаю, что обвиняемый класс - это анонимный класс, который я создал внутри докладчика, однако я не знаю, как сделать его более "публичным", чем в настоящее время. Если я заменю его именованным вложенным классом, это, похоже, сработает. Это также работает, если Presenter и EventDispatcher находятся в одном пакете, но я не могу этого допустить (несколько докладчиков в разных пакетах должны использовать EventDispatcher)

есть идеи?

4 ответа

Решение

Это ошибка в JVM ( ошибка 4819108)

Обходной путь должен позвонить method.setAccessible(true) до звонка method.invoke(listener)

Я предполагаю, что анонимный класс всегда private, но я не нашел четкого заявления об этом в Спецификации языка Java (я смотрел в §15.9.5)

В Java, если тип недоступен, его члены тоже не являются.

Если вам нравится черная магия, вы можете отключить проверку доступа, используя method.setAccessible(true), В качестве альтернативы, вы могли бы требовать, чтобы ваши обработчики событий были названы классами, или рассматриваемый метод был объявлен в доступных типах.

Проблема здесь в том, что в коде, который использует отражение, вы отражаете класс, а не интерфейс.

При не отражении обстоятельств listener не будет считаться типом presenter.Presenter$1, Вы бы использовали его через ModelChangedHandler ссылка. ModelChangedHandler является публичным типом и имеет открытый метод, и этот полиморфный доступ будет разрешен.

Но потому что вы используете getClass(), вы получаете фактический класс реализации. Обычно этот класс вообще недоступен. Локальные и анонимные классы не являются классами верхнего уровня и не являются членами. Поэтому "доступ" для них не определен.

Фактически, настоящая ошибка здесь заключается в том, что механизм отражения рассматривает "модификаторы отсутствия доступа" как "доступ по умолчанию", который является "закрытым пакетом". Так что это разрешает эту операцию, когда типы находятся в одном пакете. ИМО, он должен был сообщить 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();
        }
    }
}

Единственное изменение здесь - попытка поймать.

На этот раз - нет проблем с доступом. Метод вызывается полиморфно и совершенно доступен!

Это действительно плохая идея использовать рефлексию в этом случае. Просто позвольте вашему диспетчеру вызвать необходимый метод. Если вам нужно несколько диспетчеров для вызова разных методов, просто разбейте их на подклассы.

В Java отсутствуют замыкания, но помощь уже в пути!

Другие вопросы по тегам