id := c.ParamsInt64( ":alertId")

query := models.GetAlertByIdQuery{Id: id}

iferr := bus.Dispatch(&query); err != nil{

c.JsonApiErr( 404, "Alert not found", nil)

return

}

ifc.OrgId != query.Result.OrgId {

c.JsonApiErr( 403, "You are not allowed to edit/view alert", nil)

return

}

}

关键是 bus.Dispatch(&query) 这段代码,它的参数是一个结构体 GetAlertByIdQuery ,内容如下:

typeGetAlertByIdQuery struct{

Id int64

Result *Alert

}

根据名字可以看出这个方法就是通过Id去查询Alert,其中 Alert 结构体就是结果对象,这里就不贴出来了。

通过查看源码可以得知,Dispatch背后是调用了 GetAlertById 这个方法,然后把结果赋值到query参数的Result中返回。

funcGetAlertById(query *models.GetAlertByIdQuery)error{

alert := models.Alert{}

has, err := x.ID(query.Id).Get(&alert)

if!has {

returnfmt.Errorf( "could not find alert")

}

iferr != nil{

returnerr

}

query.Result = &alert

returnnil

}

问题来了,这是怎么实现的呢?Dispatch到底做了哪些操作?这样做有什么好处?

下面我来一一解答:

首先,在Dispatch之前,你需要先注册这个方法,也就是调用 AddHandler ,在这个项目里面可以看到init函数里面有大量这样的代码:

funcinit{

bus.AddHandler( "sql", SaveAlerts)

bus.AddHandler( "sql", HandleAlertsQuery)

bus.AddHandler( "sql", GetAlertById)

...

}

其实这个方法的逻辑也很简单,所谓注册也就是把通过一个map把函数名和对应的函数做一个映射关系保存起来,当我们Dispatch的时候其实就是通过参数名查找之前注册过的函数,然后通过反射调用该函数。

// InProcBus defines the bus structure

typeInProcBus struct{

handlers map[ string]HandlerFunc

handlersWithCtx map[ string]HandlerFunc

listeners map[ string][]HandlerFunc

txMng TransactionManager

}

下面就看看具体的源码, AddHandler 方法内容如下:

func(b *InProcBus)AddHandler(handler HandlerFunc){

handlerType := reflect.TypeOf(handler)

queryTypeName := handlerType.In( 0).Elem.Name // 获取函数第一个参数的名称,在上面例子里面就是GetAlertByIdQuery

b.handlers[queryTypeName] = handler

}

Dispatch方法的源码如下:

func(b *InProcBus)Dispatch(msg Msg)error{

varmsgName = reflect.TypeOf(msg).Elem.Name

withCtx := true

handler := b.handlersWithCtx[msgName] // 根据参数名查找注册过的函数,先查找带Ctx的handler

ifhandler == nil{

withCtx = false

handler = b.handlers[msgName]

ifhandler == nil{

returnErrHandlerNotFound

}

}

varparams = []reflect.Value{}

ifwithCtx {

// 如果查找到的handler是带Ctx的就给个默认的Background的Ctx

params = append(params, reflect.ValueOf(context.Background))

}

params = append(params, reflect.ValueOf(msg))

ret := reflect.ValueOf(handler).Call(params) // 通过反射机制调用函数

err := ret[ 0].Interface

iferr == nil{

returnnil

}

returnerr.(error)

}

对于 AddHandlerCtx 和 DispatchCtx 这个2个方法基本上是一样的,只不过多了一个上下文参数,可以拿来做超时控制或者其它用途。

2.订阅和发布

func(b *InProcBus)AddEventListener(handler HandlerFunc){

handlerType := reflect.TypeOf(handler)

eventName := handlerType.In( 0).Elem.Name

_, exists := b.listeners[eventName]

if!exists {

b.listeners[eventName] = make([]HandlerFunc, 0)

}

b.listeners[eventName] = append(b.listeners[eventName], handler)

}

查看源码可以得知,可以给一个事件注册多个handler函数,而Publish的时候则是依次调用注册的函数,逻辑也不复杂。

func(b *InProcBus)Publish(msg Msg)error{

varmsgName = reflect.TypeOf(msg).Elem.Name

varlisteners = b.listeners[msgName]

varparams = make([]reflect.Value, 1)

params[ 0] = reflect.ValueOf(msg)

for_, listenerHandler := rangelisteners {

ret := reflect.ValueOf(listenerHandler).Call(params)

e := ret[ 0].Interface

ife != nil{

err, ok := e.(error)

ifok {

returnerr

}

returnfmt.Errorf( "expected listener to return an error, got '%T'", e)

}

}

returnnil

}

3.好处

可能有人会好奇,为什么明明可以直接调用函数就行,为啥非得绕个弯子,整这么复杂?

况且,每次调用都得使用反射机制,性能也不行。

我觉得主要有以下几点:

1.这种写法逻辑清晰,解耦

2.方便单元测试

3.性能不是最大考量,虽然说反射会降低性能

转自:

wangbjun.site/2021/coding/golang/event-bus.html

转自:

wangbjun.site/2021/coding/golang/event-bus.html

- EOF -

点击标题可跳转

1、 太方便了!这款开源终端工具可查询 IP 信息 ...

2、 这个开源库有个性,里面都是垃圾代码书写准则

3、 平时的工作如何体现一个人的技术深度?

↓推荐关注↓

「Go 开发大全」参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。关注后回复 Go 获取6万star的Go资源库

点赞和在看就是最大的支持❤️

声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。