BroToolkit 中集成了插件 platform-core,BroFramework、BroBPM中都使用其事件总线(插件的其他功能都被禁用了)作为默认的事件总线(如流程提醒服务);此外,还通过 CometD 插件实现了类似的异步消息总线、尤其是浏览器和服务器之间的消息。
这里简单介绍一下常用API的基本使用,更多信息参考 Events Bus(英文)。
在 Domain、Controller、Service 类中,可以直接调用 event 方法发起一个事件:
- event ( topic, [data, params, callbackClosure] )
- event ( Map args, [callbackClosure] )
主要参数包括:
- topic: 主题,监听器通过 topic 和 namespace 来监控频道
- data: 传递给监听器的数据
- params:一些参数,Map格式,常用的有 fork(是否异步,默认为true)、namespace/for(主题所在的命名空间,默认为 app)、onError、onReply
- args:包含上面所有的Map
- callbackClosure:事件执行完后的回调闭包
个人更喜欢第二种调用方式,示例如下:
// 异步事件(多线程)
event( for: bropen.bpm.Constants.PLUGIN_NAME, topic: "taskAsync", data: [foo:1, bar: 2], fork: true )
// 同步事件(单线程,如果监听器中有异常,则会一直抛到当前线程,并导致后面的代码不再执行)
event( for: bropen.bpm.Constants.PLUGIN_NAME, topic: "taskSync", data: [foo:1, bar: 2], fork: false )
该方法返回一个 EventReply 对象(实现了Future接口),提供了下面的一些方法:
- List<Object> getValues() : 获得所有监听器的返回值
- Object getValue() : getValues() 的第一个
- int size() : 触发的监听器数量
- List<Throwable> getErrors() : 错误列表
- boolean hasErrors() : 是否有异常
- EventReply waitFor() : 等待所有监听器执行完毕
- EventReply waitFor(long time) : 等待所有监听器执行完毕、或者一定时间
需要注意的是,上面的调用任何方法,都会导致当前线程等待,除了异常处理方式不一样外,效果和同步事件类似。
此外,可以使用通配符后缀在多个频道触发事件,如:
// 所有以"role-"开头的命名空间的、以”chat-“开头的主题
event for:'role-*', topic:'chat-*', data:session.user
// app命名空间的所有主题
event '*'
有几种事件监听的方式。
常用的是在 Service 类中,使用注解 @grails.events.Listener 来注册监听方法,该方法会直接编译到class文件中,因此,需要谨慎使用常量。
示例如下:
class SomeService{
@grails.events.Listener(namespace = bropen.bpm.Constants.PLUGIN_NAME, topic = 'taskAsync')
def myMethod(Map data){
do something ...
}
}
这里的监听方法一般只有一个参数,表明要监听频道的数据类型(如果相同频道会发送不同类型的数据,可以使用参数类型进行过滤,或者使用 Object 类型监听所有);此外,还可以使用 EventMessage 类型的参数来监听所有。
同样,这里的命名空间、主题参数也支持通配符后缀,如:
@grails.events.Listener(namespace = "role-*", topic = 'chat-*')
其次,可以在 Domain、Controller、Service 类中,调用 on 方法来动态注册一个监听器,如:
class User() {
def bootStrapInit() {
// 监听命名空间 foobar 下的 taskAsync 事件
on("foobar", "taskAsync") { Map data->
do something...
}
// 监听命名空间 app 下的 taskAsync 事件
on("taskAsync") {
do something...
}
}
}
每一个监听器会生成一个唯一ID,格式为: [namespace://]topic[:package.Class][#method][@hashcode]。可以通过这个ID来统计、删除监听器,如:
//count every listeners subscribed to 'mytopic' inside TestService
countListeners("mytopic:my.TestService")
//count every listeners using gorm namespace
countListeners("gorm://*")
//remove every listeners in TestService
removeListeners("*:my.TestService")
在Grails中,可以通过多种方式来监听Domain实例的增删改查等事件,比如Domain类中的 afterInsert 等事件、通用的自定义事件(Custom Event Listeners)、通用的Hibernate事件(Hibernate Events),详见官方手册。
这里,还可以通过命名空间 gorm 来监听Domain实例的增删改操作,如:
class SomeService{
@Listener(namespace = 'gorm')
void afterInsert(Author author) {
println "after save author - $author.name"
}
@Listener(topic = 'beforeInsert', namespace = 'gorm')
void beforeInsertBook(Book book) {
println "will insert book - $book.title"
}
//Will catch everything since we don't filter on the subject by using EventMessage
@Listener(topic = 'before*', namespace = 'gorm')
void beforeEachGormEvent(EventMessage message) {
println "gorm event $message.event on domain $message.data.class"
}
}
此外,还可以通过 Events Artifact 来实现更细粒度的事件过滤。