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

StackOverflow https://stackoverflow.com/questions/2659517

Вопрос

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

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

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 отсутствует закрытие, но помощь в пути!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top