How do annotations + Listener work internally?

Discussion in 'Spigot Plugin Development' started by Dimon6, Jun 28, 2015.

  1. Im wondering what title says cause I saw the Listener interface does not have any method so I guess @EventHandler does some job there.
    Could anyone explain me how it does work exactly and how Spigot knows when you are handling an event?
    For example, I write my own handling method and I pass an Event. I guess Spigot knows Im handling an event cause the class is a Listener + the handlier is annotated + I passed an event to the handler.
    Does it work somewhat like that?
    Seems like events don't work 100% exactly like Java Swing events and listeners.
     
  2. When you register a listener class with registerEvents(Listener, Plugin), that method uses reflection to get all of the methods in the listener class, checks which have an @EventHandler annotation and a single argument that extends Event and stores them in a registry so it can call them when the various events occur.

    If you want to know exactly what it does, here is the relevant code that does the analysis of the listener class:
    PHP:
        public Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(Listener listener, final Plugin plugin) {
            Validate.notNull(plugin, "Plugin can not be null");
            Validate.notNull(listener, "Listener can not be null");

            boolean useTimings = server.getPluginManager().useTimings();
            Map<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<Class<? extends Event>, Set<RegisteredListener>>();
            Set<Method> methods;
            try {
                Method[] publicMethods = listener.getClass().getMethods();
                Method[] privateMethods = listener.getClass().getDeclaredMethods();
                methods = new HashSet<Method>(publicMethods.length + privateMethods.length, 1.0f);
                for (Method method : publicMethods) {
                    methods.add(method);
                }
                for (Method method : privateMethods) {
                    methods.add(method);
                }
            } catch (NoClassDefFoundError e) {
                plugin.getLogger().severe("Plugin " + plugin.getDescription().getFullName() + " has failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist.");
                return ret;
            }

            for (final Method method : methods) {
                final EventHandler eh = method.getAnnotation(EventHandler.class);
                if (eh == null) continue;
                // Do not register bridge or synthetic methods to avoid event duplication
                // Fixes SPIGOT-893
                if (method.isBridge() || method.isSynthetic()) {
                    continue;
                }
                final Class<?> checkClass;
                if (method.getParameterTypes().length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) {
                    plugin.getLogger().severe(plugin.getDescription().getFullName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass());
                    continue;
                }
                final Class<? extends Event> eventClass = checkClass.asSubclass(Event.class);
                method.setAccessible(true);
                Set<RegisteredListener> eventSet = ret.get(eventClass);
                if (eventSet == null) {
                    eventSet = new HashSet<RegisteredListener>();
                    ret.put(eventClass, eventSet);
                }

                for (Class<?> clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
                    // This loop checks for extending deprecated events
                    if (clazz.getAnnotation(Deprecated.class) != null) {
                        Warning warning = clazz.getAnnotation(Warning.class);
                        WarningState warningState = server.getWarningState();
                        if (!warningState.printFor(warning)) {
                            break;
                        }
                        plugin.getLogger().log(
                                Level.WARNING,
                                String.format(
                                        "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated." +
                                        " \"%s\"; please notify the authors %s.",
                                        plugin.getDescription().getFullName(),
                                        clazz.getName(),
                                        method.toGenericString(),
                                        (warning != null && warning.reason().length() != 0) ? warning.reason() : "Server performance will be affected",
                                        Arrays.toString(plugin.getDescription().getAuthors().toArray())),
                                warningState == WarningState.ON ? new AuthorNagException(null) : null);
                        break;
                    }
                }

                EventExecutor executor = new EventExecutor() {
                    public void execute(Listener listener, Event event) throws EventException {
                        try {
                            if (!eventClass.isAssignableFrom(event.getClass())) {
                                return;
                            }
                            method.invoke(listener, event);
                        } catch (InvocationTargetException ex) {
                            throw new EventException(ex.getCause());
                        } catch (Throwable t) {
                            throw new EventException(t);
                        }
                    }
                };
                if (useTimings) {
                    eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
                } else {
                    eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
                }
            }
            return ret;
        }
     
    #3 St3venAU, Jun 28, 2015
    Last edited: Jun 28, 2015
  3. Thanks both of you :)