исключение доступа при вызове метода анонимного класса с использованием отражения Java - PullRequest
5 голосов
/ 17 апреля 2010

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

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

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

Main:

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 ]

10 голосов
/ 17 апреля 2010

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

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

1 голос
/ 23 ноября 2015

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

В условиях отсутствия отражения 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();
        }
    }
}

Единственное изменение здесь - это try-catch.

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

1 голос
/ 17 апреля 2010

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

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

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

0 голосов
/ 17 апреля 2010

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

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

...