前言
上次介绍了如何在 Kotlin 语言中使用 DSL 构造注册命令,这篇来写一下注册事件监听器。
注册监听器通常情况下都是用 @EventHandler
标注一个方法,写在一个实现空接口 Listener
的类中,像这样:
1 | object DemoListener : Listener { |
在插件启动时写上:
1 | pluginManager.registerEvents(DemoListener, plugin) |
就可以正常食用了。如果不通过注解反射实现呢?我们需要翻一下源码,看看他的底层是怎么实现的。
Bukkit 源码部分
可以在 Bukkit 这里找到它的源码。拿到源码后我们从 registerEvents
这里入手,看看它帮我们干了什么不可描述的事情。PluginManager
是个接口,需要到它的实现类 SimplePluginManager
中找。
1 | public void registerEvents(Listener listener, Plugin plugin) { |
可以看到关键在于掉用了 createRegisteredListeners
这个方法,然后把 RegisteredListeners
和 Event
对应交给 HandlerList
处理。(createRegisteredListeners
是 PluginLoader
接口的方法)同样,我们需要找他的实现类 JavaPluginLoader
。这个方法有 87 行,这里简述一下其才做流程:
- 拿到
Listener
实例后,用反射找里面的Method
; - 找到 Method 后检验是否符合注册的标准,并且拿到其注解
@EventHandler
中的优先级; - 对于被标注
@Deprecated
并符合要求的方法要在 logger 中打印出来,提醒使用者; - 建立一个
EventExecutor
接口的匿名类实例,将方法execute
重写,在其中调用反射找来的方法 (method.invoke(listener, event)
); - 根据
useTimings
分别new 出TimedRegisteredListener
或RegisteredListener
,并把它们加到与Event
对应的 Map 中。
交给 HandlerList
后,其中有一个 EnumMap<EventPriority, ArrayList<RegisteredListener>>
来保存每一个优先级对应的监听器,在 PluginManager
的 fireEvent
方法中调用。看到这我们大概了解了它的注册原理,EventExecutor
这个东西才是最关键的。我们回到 PluginManager
,除了有 registerEvents(Listener listener, Plugin plugin)
外,还有两个注册方法:
1 | public void registerEvent(Class<? extends Event> event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin) { |
对比一下刚才的 registerEvents
,我们发现它只是少了用反射找那些 method 的部分,并且让我们传入一个 EventExecutor
实例。这就很简单了,不过我一直没想明白为啥它还要求一个 Listener
实例。。。但是这不重要,传个空的就好,因为在 RegisteredListener
构造时也没有对 Listener
做出什么不可描述的事情。试着用一下:
1 | pluginManager.registerEvent(PlayerJoinEvent::class.java, object : Listener {}, |
这里第三个参数应该是 EventExecutor
,但此接口符合函数式接口的标准,Kotlin 帮我们 SAM 转换成 (Listener,Event) -> Unit
。写好后放到服务器里运行,发现插件可以正常工作,下面就可以开始写 DSL 语言结构了。
DSL 构造事件监听器
与上篇说的命令相似,我们同样需要把监听器封装一下,让他持有注册时所需要的参数:
1 | data class PackingEvent<in T : Event>(private val type: Class<out Event>, |
在 Kotlin 中可以用数据类进行封装,不过没获得什么好处。因为我们传进去的是空 Listener
,所以没必要再把一个空的拿回来。将 event
cast 成 T
,这里不用担心 cast 出错,因为注册的是什么事件,传进来的一定是想要的,类型不会错。有了 PackingEvent
,就可以创造 Builder 了。但我们发现,与命令不同,注册事件没有那些可有可无的东西,并且只需要优先级和一个 (T) -> Unit
。这样就没有必要再写 Builder 了,直接写个 Scope:
1 | class EventScope { |
只有一个函数,为了获取泛型的类实例,要写成 inline
+ reified
。但 block : (T) -> Unit
会被存到 PackingEvent
中,并非原地调用,不能被编译器内联优化,所以 block
要加上关键字 noinline
。
再把 Scope 开放出来:
1 | fun buildEvents(block: EventScope.() -> Unit) = |
然后就可以愉快的食用了,在插件启动时写上:
1 | buildEvents { |
event
函数中直接写成了 T.() -> Unit
并且 apply(block)
这个大括号的里面就相当于对应事件的类中,可以直接访问里面的公共成员。优先级上面定义了如果不声明默认是 NORMAL
。
总结
还是说几个问题:
- 无法动态注册、取消注册
- 封装性欠缺