Исключение доступа при вызове метода анонимного класса с использованием отражения Java
-
27-09-2019 - |
Вопрос
Я пытаюсь использовать диспетчер событий, чтобы позволить модели уведомлять подписанных слушателей, когда она меняется. Диспетчер событий принимает класс обработчика и имя метода для вызова во время отправки. Докладчик подписывается на модуль изменений и предоставляет обрабатывать реализацию обработчика для изменений.
Вот код (извините, это немного долго).
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.EVENTDISPATCER не может получить доступ к члену класса Presenter. Представляете 1 доллар с модификаторами «публики».
Я понимаю, что класс виноваты - это анонимный класс, который я создал внутри докладчика, однако я не знаю, как сделать это уже более «публичным», чем в настоящее время. Если я заменил его с именем вложенного класса, кажется, работает. Он также работает, если ведущий и EventDispatcher находятся в одном пакете, но я не могу позволить (несколько докладчиков в разных пакетах должны использовать EventDispatcher)
Любые идеи?
Решение
Это ошибка в JVM (BUG 4819108.)
Обходной путь должен звонить method.setAccessible(true)
до звонка method.invoke(listener)
Другие советы
Я думаю, что анонимный класс всегда private
, но я не нашел четкое заявление об этом в спецификации языка Java (я посмотрел в § 15.9.5)
В Java, если тип не доступен, ни его члены.
Если вам нравится черная магия, вы можете отключить проверку доступа, используя method.setAccessible(true)
. Отказ Alternativly, вы можете потребовать, чтобы ваши обработчики событий были названы классами, или метод, имеющий вопрос, объявленный в доступных типах.
Проблема здесь заключается в том, что в коде, который использует отражение, вы отражаете класс, а не интерфейс.
При неразрывных обстоятельствах, listener
не будет считаться типом presenter.Presenter$1
. Отказ Вы бы использовали его через ModelChangedHandler
Справка. ModelChangedHandler
Это публичный тип, и он имеет общественный метод, и этот полиморфный доступ будет допущен.
Но потому что вы используете getClass()
, Вы получаете фактический класс реализации. Обычно этот класс вообще не доступен. Местные и анонимные классы не являются верхним уровнем, а не классы участников. Поэтому «доступ» не определен для них.
На самом деле, реальная ошибка здесь - это тот факт, что механизм отражения рассматривает «без модификаторов доступа» как «Доступ по умолчанию», который является «Package Parttive». Так что это позволяет этой операции, когда типы находятся в одном пакете. ИМО, это должно было сообщить 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());
}
}
}
}
В этой версии мы передаем конструктор классовой объект для требуемого интерфейса, а также имя метода. Мы создаем 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 отсутствует закрытие, но помощь в пути!