Access exception when calling anonymous class method using java reflection

I am trying to use the event manager to allow the model to notify subscribers with a subscription when it changes. the event manager gets the handler class and method name to call at the time of dispatch. the facilitator subscribes to model changes and provides a Handler implementation for making changes.

Here is the code (I'm sorry it is a little 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(); } 

Leading:

 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 receive the message "model changed". However, I get java.lang.IllegalAccessException: class utils.EventDispatcher cannot access a member of class presenter.Presenter $ 1 with modifiers "public".

I understand that the class is to blame for the anonymous class i created inside the host, but I don’t know how to make it more “public” than at present. If I replaced it with the named nested class, it seems to work. It also works if Presenter and EventDispatcher are in the same package, but I cannot allow it (several speakers in different packages should use EventDispatcher)

any ideas?

+5
source share
4 answers

This is a bug in the JVM ( bug 4819108 )

The method.setAccessible(true) is to call method.setAccessible(true) before calling method.invoke(listener)

+9
source

I assume that the anonymous class is always private , but I did not find a clear statement about this in the Java Language Specification (I looked at §15.9.5)

In Java, if a type is not available, none of its members.

If you like black magic, you can turn off access checks using method.setAccessible(true) . Alternatively, you can require that the event handlers be called classes, or the method in question is declared in accessible types.

+1
source

The problem is that in code that uses reflection, you are reflecting a class, not an interface.

In the absence of reflection, the listener will not be considered the type presenter.Presenter$1 . You would use it using the ModelChangedHandler link. ModelChangedHandler is a public type and has a public method and polymorphic access is allowed.

But since you use getClass() , you get the actual implementation class. Usually this class is not available at all. Local and anonymous classes are not top-level classes, not members. Therefore, “access” is not defined for them.

In fact, the real mistake is that the reflection mechanism treats “without access modifiers” as “default access”, which is a “private package”. Thus, it allows this operation when the types are in the same package. IMO, it should have reported an IllegalAccessException , even if they are in the same package, since there is no access to this class from where you are calling it, and access restriction should be explicitly removed using method.setAccessible(true) .

So, what would be a more proper way to do this? You must access it using the Class interface.

 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 to the constructor a class object for the required interface, as well as the name of the method. We create a Method object in the constructor. This is a reflection of the method in the interface itself. Not a class.

In dispatch , when we call a method, we apply an interface method to this listener. This reflection is 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 Model , we use an interface class literal that 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 a try-catch attempt.

This time, access problems. The method is called polymorphically and is perfectly accessible!

+1
source

It is a really bad idea to use reflection in this case. Just let your dispatcher call the required method. If you need multiple dispatchers to call different methods, just subclass them.

Java lacks closures, but help is on the way!

0
source

All Articles