Исключение доступа при вызове метода анонимного класса с использованием отражения 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 отсутствуют замыкания, но помощь уже в пути!