William_Shi
本帖最后由 William_Shi 于 2020-6-28 22:26 编辑

本文纯属Bukkit源码研究
起因是有一个新人问了Bukkit怎么知道某方法是事件监听处理方法的
本文其实就是一个思路的梳理

该从哪里入手?
我们都知道,注册事件监听的过程是PluginManager#registerEvent(或者是events)
https://bukkit.windit.net/javado ... kkit.plugin.Plugin-

那么我们就从PluginManager的实现类入手


其中的注册监听的一个方法如下


首先是检查插件有没有被启用,如果是被disable的插件那就没必要注册事件监听了
之后呢?
Map.Entry<Class<? extends Event>, Set<RegisteredListener>>
这是Map.Entry<K,V>
也就是遍历Map的操作
遍历了plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()
这又是一个什么Map呢

我们接下来看org.bukkit.plugin.java.JavaPluginLoader
这是PluginLoader的具体实现类



别急,仔细看
    Map<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<>();
简写return为ret,这是创建map的操作

      Method[] publicMethods = listener.getClass().getMethods();
      Method[] privateMethods = listener.getClass().getDeclaredMethods();
      methods = new HashSet<>(publicMethods.length + privateMethods.length, 1.0F);

说白了就是把你整个实现Listener类的所有方法给存起来了(具体存储的,直接看上面)
接下来最最最最重要的,@Interface,给方法的注解

    for (Method method : methods) {
      EventHandler eh = method.<EventHandler>getAnnotation(EventHandler.class);
      if (eh == null)
        continue;

这里就检查了,方法有没有@EventHandler这个注解
如果有,那么再进行下一步添加事件监听的操作,否则就忽略

if ((method.getParameterTypes()).length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) {
        plugin.getLogger().severe(String.valueOf(plugin.getDescription().getFullName()) + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass());
        continue;
      }

这个是什么?这是检查参数的长度
那就可以回答一个问题https://www.mcbbs.net/forum.php? ... 067519&pid=18709678
这个帖子的第二个事件监听

        @EventHandler
        void onBreakPickaxe(PlayerItemBreakEvent e,Plugin plugin) {

会在register的时候被过滤掉,因为参数列表长度不是1
(Bukkit不可能知道你的奇奇怪怪的参数,他只能给你Event对象,所以必须过滤

不要乱,我们回到起点
for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet())
      getEventListeners(getRegistrationClass(entry.getKey())).registerAll(entry.getValue());

再来看




我们接触到了https://bukkit.windit.net/javado ... nt/HandlerList.html

private HandlerList getEventListeners(@NotNull Class<? extends Event> type)
注意变量名Type
什么意思?每一个事件的类型,都有自己的对应的Handler,
这些Handler,按照事件监听的优先级,存储了事件监听的处理器
当callEvent时,按照优先级,一批一批触发

public class HandlerList {

  private final EnumMap<EventPriority, ArrayList<RegisteredListener>> handlerslots;

所以,上面在遍历了Map之后(不要乱掉,仔细思考)
其实就是获取了对应的EventHandler注解的方法的相关参数
得到了事件本身的Class,也就是事件的类型
然后获取这个事件的Handler
最后在这个Handler里面加入事件监听器
当callEvent的时候,就按照优先级去调用
至于@Interface的相关问题 可以看看@蕾米洛伊 的详尽回复
https://www.mcbbs.net/forum.php? ... 067519&pid=18709785
那么现在可能还有一个问题:到底哪里触发了事件监听???
这都是OBC的CraftEventFactory以及具体的NMS类的实现了
举个例子(Spigot1.15.2核心)
EntityItem类(物品堆掉落物实体)128行开始

      if (!this.world.isClientSide && this.age >= this.world.spigotConfig.itemDespawnRate) {
        if (CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
          this.age = 0;
          return;
        }
        die();
      }


2021.12 数据,可能有更多内容本文纯属Bukkit源码研究
起因是有一个新人问了Bukkit怎么知道某方法是事件监听处理方法的
本文其实就是一个思路的梳理


该从哪里入手?
我们都知道,注册事件监听的过程是PluginManager#registerEvent(或者是events)
https://bukkit.windit.net/javado ... kkit.plugin.Plugin-


那么我们就从PluginManager的实现类入手


package org.bukkit.plugin;
public final class SimplePluginManager implements PluginManager


其中的注册监听的一个方法如下


public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin) {
    if (!plugin.isEnabled())
   throw new IllegalPluginAccessException(&quot;Plugin attempted to register &quot; + listener + &quot; while not enabled&quot;);
    for (Map.Entry&lt;Class&lt;? extends Event&gt;, Set&lt;RegisteredListener&gt;&gt; entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet())
   getEventListeners(getRegistrationClass(entry.getKey())).registerAll(entry.getValue());
}


首先是检查插件有没有被启用,如果是被disable的插件那就没必要注册事件监听了
之后呢?
Map.Entry&lt;Class&lt;? extends Event&gt;, Set&lt;RegisteredListener&gt;&gt;
这是Map.Entry&lt;K,V&gt;
也就是遍历Map的操作
遍历了plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()
这又是一个什么Map呢


我们接下来看org.bukkit.plugin.java.JavaPluginLoader
这是PluginLoader的具体实现类


@NotNull
public Map&lt;Class&lt;? extends Event&gt;, Set&lt;RegisteredListener&gt;&gt; createRegisteredListeners(@NotNull Listener listener, @NotNull Plugin plugin) {
    Set&lt;Method&gt; methods;
    Validate.notNull(plugin, &quot;Plugin can not be null&quot;);
    Validate.notNull(listener, &quot;Listener can not be null&quot;);
    boolean useTimings = this.server.getPluginManager().useTimings();
    Map&lt;Class&lt;? extends Event&gt;, Set&lt;RegisteredListener&gt;&gt; ret = new HashMap&lt;&gt;();
    try {
   Method[] publicMethods = listener.getClass().getMethods();
   Method[] privateMethods = listener.getClass().getDeclaredMethods();
   methods = new HashSet&lt;&gt;(publicMethods.length + privateMethods.length, 1.0F);
   byte b;
   int i;
   Method[] arrayOfMethod1;
   for (i = (arrayOfMethod1 = publicMethods).length, b = 0; b &lt; i; ) {
  final Method method = arrayOfMethod1;
  methods.add(method);
  b++;
   }
   for (i = (arrayOfMethod1 = privateMethods).length, b = 0; b &lt; i; ) {
  final Method method = arrayOfMethod1;
  methods.add(method);
  b++;
   }
    } catch (NoClassDefFoundError e) {
   plugin.getLogger().severe(&quot;Plugin &quot; + plugin.getDescription().getFullName() + &quot; has failed to register events for &quot; + listener.getClass() + &quot; because &quot; + e.getMessage() + &quot; does not exist.&quot;);
   return ret;
    }
    for (Method method : methods) {
   EventHandler eh = method.&lt;EventHandler&gt;getAnnotation(EventHandler.class);
   if (eh == null)
  continue;
   if (method.isBridge() || method.isSynthetic())
  continue;
   Class&lt;?&gt; checkClass;
   if ((method.getParameterTypes()).length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) {
  plugin.getLogger().severe(String.valueOf(plugin.getDescription().getFullName()) + &quot; attempted to register an invalid EventHandler method signature \&quot;&quot; + method.toGenericString() + &quot;\&quot; in &quot; + listener.getClass());
  continue;
   }
   final Class&lt;? extends Event&gt; eventClass = checkClass.asSubclass(Event.class);
   method.setAccessible(true);
   Set&lt;RegisteredListener&gt; eventSet = ret.get(eventClass);
   if (eventSet == null) {
  eventSet = new HashSet&lt;&gt;();
  ret.put(eventClass, eventSet);
   }
   for (Class&lt;?&gt; clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
  if (clazz.getAnnotation(Deprecated.class) != null) {
    Warning warning = clazz.&lt;Warning&gt;getAnnotation(Warning.class);
    Warning.WarningState warningState = this.server.getWarningState();
    if (!warningState.printFor(warning))
   break;
    plugin.getLogger().log(
  Level.WARNING,
  String.format(
    &quot;\&quot;%s\&quot; has registered a listener for %s on method \&quot;%s\&quot;, but the event is Deprecated. \&quot;%s\&quot;; please notify the authors %s.&quot;, new Object[] { plugin.getDescription().getFullName(),
   clazz.getName(),
   method.toGenericString(), (
   warning != null &amp;&amp; warning.reason().length() != 0) ? warning.reason() : &quot;Server performance will be affected&quot;,
   Arrays.toString(plugin.getDescription().getAuthors().toArray()) }), (warningState == Warning.WarningState.ON) ? (Throwable)new AuthorNagException(null) : null);
    break;
  }
   }
   final CustomTimingsHandler timings = new CustomTimingsHandler(&quot;Plugin: &quot; + plugin.getDescription().getFullName() + &quot; Event: &quot; + listener.getClass().getName() + &quot;::&quot; + method.getName() + &quot;(&quot; + eventClass.getSimpleName() + &quot;)&quot;, pluginParentTimer);
   EventExecutor executor = new EventExecutor() {
    public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException {
   try {
  if (!eventClass.isAssignableFrom(event.getClass()))
    return;
  boolean isAsync = event.isAsynchronous();
  if (!isAsync)
    timings.startTiming();
  method.invoke(listener, new Object[] { event });
  if (!isAsync)
    timings.stopTiming();
   } catch (InvocationTargetException ex) {
  throw new EventException(ex.getCause());
   } catch (Throwable t) {
  throw new EventException(t);
   }
    }
  };
   eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
    }
    return ret;
}



别急,仔细看
    Map&lt;Class&lt;? extends Event&gt;, Set&lt;RegisteredListener&gt;&gt; ret = new HashMap&lt;&gt;();
简写return为ret,这是创建map的操作


   Method[] publicMethods = listener.getClass().getMethods();
   Method[] privateMethods = listener.getClass().getDeclaredMethods();
   methods = new HashSet&lt;&gt;(publicMethods.length + privateMethods.length, 1.0F);


说白了就是把你整个实现Listener类的所有方法给存起来了(具体存储的,直接看上面)
接下来最最最最重要的,@Interface,给方法的注解


    for (Method method : methods) {
   EventHandler eh = method.&lt;EventHandler&gt;getAnnotation(EventHandler.class);
   if (eh == null)
  continue;


这里就检查了,方法有没有@EventHandler这个注解
如果有,那么再进行下一步添加事件监听的操作,否则就忽略


if ((method.getParameterTypes()).length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) {
  plugin.getLogger().severe(String.valueOf(plugin.getDescription().getFullName()) + &quot; attempted to register an invalid EventHandler method signature \&quot;&quot; + method.toGenericString() + &quot;\&quot; in &quot; + listener.getClass());
  continue;
   }


这个是什么?这是检查参数的长度
那就可以回答一个问题https://www.mcbbs.net/forum.php? ... 067519&amp;pid=18709678
这个帖子的第二个事件监听


  @EventHandler
  void onBreakPickaxe(PlayerItemBreakEvent e,Plugin plugin) {


会在register的时候被过滤掉,因为参数列表长度不是1
(Bukkit不可能知道你的奇奇怪怪的参数,他只能给你Event对象,所以必须过滤


不要乱,我们回到起点
for (Map.Entry&lt;Class&lt;? extends Event&gt;, Set&lt;RegisteredListener&gt;&gt; entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet())
   getEventListeners(getRegistrationClass(entry.getKey())).registerAll(entry.getValue());


再来看

@NotNull
private HandlerList getEventListeners(@NotNull Class&lt;? extends Event&gt; type) {
    try {
   Method method = getRegistrationClass(type).getDeclaredMethod(&quot;getHandlerList&quot;, new Class[0]);
   method.setAccessible(true);
   return (HandlerList)method.invoke(null, new Object[0]);
    } catch (Exception e) {
   throw new IllegalPluginAccessException(e.toString());
    }
}



@NotNull
private Class&lt;? extends Event&gt; getRegistrationClass(@NotNull Class&lt;? extends Event&gt; clazz) {
    try {
   clazz.getDeclaredMethod(&quot;getHandlerList&quot;, new Class[0]);
   return clazz;
    } catch (NoSuchMethodException e) {
   if (clazz.getSuperclass() != null &amp;&amp;
  !clazz.getSuperclass().equals(Event.class) &amp;&amp;
  Event.class.isAssignableFrom(clazz.getSuperclass()))
  return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class));
   throw new IllegalPluginAccessException(&quot;Unable to find handler list for event &quot; + clazz.getName() + &quot;. Static getHandlerList method required!&quot;);
    }
}


我们接触到了https://bukkit.windit.net/javado ... nt/HandlerList.html


private HandlerList getEventListeners(@NotNull Class&lt;? extends Event&gt; type)
注意变量名Type
什么意思?每一个事件的类型,都有自己的对应的Handler,
这些Handler,按照事件监听的优先级,存储了事件监听的处理器
当callEvent时,按照优先级,一批一批触发


public class HandlerList {


private final EnumMap&lt;EventPriority, ArrayList&lt;RegisteredListener&gt;&gt; handlerslots;


所以,上面在遍历了Map之后(不要乱掉,仔细思考)
其实就是获取了对应的EventHandler注解的方法的相关参数
得到了事件本身的Class,也就是事件的类型
然后获取这个事件的Handler
最后在这个Handler里面加入事件监听器
当callEvent的时候,就按照优先级去调用
至于@Interface的相关问题 可以看看@蕾米洛伊 的详尽回复
https://www.mcbbs.net/forum.php? ... 067519&amp;pid=18709785

那么现在可能还有一个问题:到底哪里触发了事件监听???
这都是OBC的CraftEventFactory以及具体的NMS类的实现了
举个例子(Spigot1.15.2核心)
EntityItem类(物品堆掉落物实体)128行开始


   if (!this.world.isClientSide &amp;&amp; this.age &gt;= this.world.spigotConfig.itemDespawnRate) {
  if (CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
    this.age = 0;
    return;
  }
  die();
   }


古明地兔
加個[code吧],看著實在不舒服

William_Shi
🐚♥ 发表于 2020-6-27 11:38
加個[code吧],看著實在不舒服

论坛的 [code插件] 吞代码的现象太严重了。。。很久以前贺兰写了半个小时的bungee config的详解全吞了,我写过的一个papi教程也吞过,总之code就不靠谱

反正这些都是反编译SDK拿到的源码,在jdgui、fernflower核心的idea等等地方都可以看

结城希亚
William_Shi 发表于 2020-6-27 12:21
论坛的 [code插件] 吞代码的现象太严重了。。。很久以前贺兰写了半个小时的bungee config的详解全吞了, ...

只要粘贴过去用就没事
不要直接改已经整好的代码(纯文本也没事)

LinKart
秃头警告qwq 再掉就没了!!

第一页 上一页 下一页 最后一页