I'm trying to use an event dispatcher to allow a model to notify subscribed listeners when it changes. the event dispatcher receives a handler class and a method name to call during dispatch. the presenter subscribes to the model changes and provide a Handler implementation to be called on changes.
Here's the code (I'm sorry it's a bit long).
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());
}
}
}
}
Model:
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();
}
Presenter:
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();
}
}
Now, I expect to get the "model changed" message. However, I'm getting an java.lang.IllegalAccessException: Class utils.EventDispatcher can not access a member of class presenter.Presenter$1 with modifiers "public".
I understand that the class to blame is the anonymous class i created inside the presenter, however I don't know how to make it any more 'public' than it currently is. If i replace it with a named nested class it seem to work. It also works if the Presenter and the EventDispatcher are in the same package, but I can't allow that (several presenters in different packages should use the EventDispatcher)
any ideas?
This is a bug in the JVM (bug 4819108)
The workaround is to call method.setAccessible(true)
before the call to method.invoke(listener)
My guess is that an anonymous class is always private
, but I didn't find a clear statement about this in the Java Language Specification (I looked in §15.9.5)
In Java, if a type is not accessible, neither are its members.
If you like black magic, you can disable access checking using method.setAccessible(true)
. Alternativly, you could require your event handlers to be named classes, or the method in question being declared in accessible types.
The problem here is that in the code that uses reflection, you are reflecting the class rather than the interface.
Under non-reflection circumstances, the listener
would not be considered to be of type presenter.Presenter$1
. You would be using it through a ModelChangedHandler
reference. ModelChangedHandler
is a public type and it has a public method, and that polymorphic access would be allowed.
But because you are using getClass()
, you are getting the actual implementing class. Normally, this class is not accessible at all. Local and anonymous classes are not top-level and not member classes. Therefore "access" is not defined for them.
In fact, the real bug here is the fact that the reflection mechanism views the "no access modifiers" as "default access" which is "package private". So it allows this operation when the types are in the same package. IMO, it should have reported IllegalAccessException
even when they are in the same package, as there is no access to the given class from where you are calling it, and the access restriction should explicitly be lifted with method.setAccessible(true)
.
So what would be the more correct way of doing this? You should access it using the interface Class
object.
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());
}
}
}
}
In this version, we pass the constructor a Class object for the required interface, as well as the method name. We create the Method
object in the constructor. It is a reflection of the method in the interface itself. Not the class.
In dispatch
, when we invoke the method, we apply the interface's method to the given listener. This is reflection combined with polymorphism.
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();
}
}
So here in the Model
, we use the interface's class literal - which we know because it is here that we decide which interface to use.
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();
}
}
}
The only change here is the try-catch.
This time - no access problems. The method is invoked polymorphically, and is perfectly accessible!
This is a really bad idea to use reflection in that case. Just let your dispatcher call the required method. If you need several dispatchers to call different methods, just subclass them.
Java is missing closures but help is on the way!