Pergunta

Estou tentando usar um despachante de evento para permitir que um modelo notifique os ouvintes assinados quando ele mudar. O despachante do evento recebe uma classe de manipulador e um nome de método a ser chamado durante o despacho. O apresentador assina as alterações do modelo e fornece uma implementação do manipulador a ser convocada em alterações.

Aqui está o código (lamento que seja um pouco longo).

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

Modelo:

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

Apresentador:

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

Principal:

package main;

public class Main {
    public static void main(String[] args) {
        Model model = new Model();
        Presenter presenter = new Presenter(model);
        model.change();
    }
}

Agora, espero fazer com que a mensagem "Modelo alterasse". No entanto, estou recebendo um java.lang.illegalaccescessception: classe utils.eventdispatcher não pode acessar um membro do apresentador de classe.

Entendo que a classe de culpa é a aula anônima que eu criei dentro do apresentador, mas não sei como torná -la mais 'pública' do que atualmente. Se eu o substituir por uma classe aninhada nomeada, parece funcionar. Ele também funciona se o apresentador e o EventDispatcher estiverem no mesmo pacote, mas não posso permitir que (vários apresentadores em diferentes pacotes devem usar o EventDispatcher)

alguma ideia?

Foi útil?

Solução

Este é um bug na JVM (Bug 4819108)

A solução alternativa é ligar method.setAccessible(true) Antes da chamada para method.invoke(listener)

Outras dicas

Meu palpite é que uma aula anônima é sempre private, mas não encontrei uma declaração clara sobre isso na especificação da linguagem Java (olhei no §15.9.5)

Em Java, se um tipo não estiver acessível, nem seus membros.

Se você gosta de magia negra, pode desativar a verificação de acesso usando method.setAccessible(true). Alternative, você pode exigir que seus manipuladores de eventos sejam nomeados classes ou o método em questão que está sendo declarado em tipos acessíveis.

O problema aqui é que, no código que usa a reflexão, você está refletindo a classe e não a interface.

Em circunstâncias não reflexões, o listener não seria considerado do tipo presenter.Presenter$1. Você estaria usando isso através de um ModelChangedHandler referência. ModelChangedHandler é um tipo público e possui um método público e que o acesso polimórfico seria permitido.

Mas porque você está usando getClass(), você está recebendo a classe de implementação real. Normalmente, essa classe não é acessível. As aulas locais e anônimas não são classes de nível superior e não membros. Portanto, o "acesso" não é definido para eles.

De fato, o bug real aqui é o fato de que o mecanismo de reflexão veja os "modificadores de acesso" como "acesso padrão", que é "Pacote privado". Portanto, permite esta operação quando os tipos estão no mesmo pacote. IMO, deveria ter relatado IllegalAccessException Mesmo quando eles estão no mesmo pacote, pois não há acesso à classe especificada de onde você está chamando, e a restrição de acesso deve ser explicitamente levantada com method.setAccessible(true).

Então, qual seria a maneira mais correta de fazer isso? Você deve acessá -lo usando o interface Class objeto.

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

Nesta versão, passamos ao construtor um objeto de classe para a interface necessária, bem como o nome do método. Nós criamos o Method objeto no construtor. É um reflexo do método no interface em si. Não a classe.

Dentro dispatch, quando invocamos o método, aplicamos o Interface Método para o ouvinte fornecido. Isso é reflexão combinada com polimorfismo.

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

Então, aqui no Model, usamos a classe literal da interface - que sabemos porque é aqui que decidimos qual interface usar.

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

A única mudança aqui é o Try-Catch.

Desta vez - sem problemas de acesso. O método é invocado polimorficamente e é perfeitamente acessível!

Esta é uma ideia muito ruim para usar a reflexão nesse caso. Basta deixar seu despachante chamar o método necessário. Se você precisar de vários despachantes para ligar para diferentes métodos, basta subclassem.

Java está faltando fechamentos, mas a ajuda está a caminho!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top