「Go 框架」深入理解 iris 中的 mvc 之原理篇
一、mvc 的基本使用
在 iris 中,还封装了 mvc 包,该包可以让开发者快速的搭建出基于 mvc(model-view-controller)分层的业务系统。其基本使用如下:
这样,就能搭建一个最简单的 controller 处理器了。然后运行该服务,在浏览器中输入 http://localhost:8080/home
就能访问到testController
的GetHome
方法。
我们知道,在 iris 框架中,是需要注册路由的,即将路径对应到特定的context.Handler类型
(函数类型)的处理器,当用户访问对应的路径时,才能执行对应处理器上的函数体的逻辑的。那么,mvc包
是如何做到路径到controller
中函数的映射的呢?
二、mvc 的实现原理
通过上面使用基于mvc包
的示例,我们可以了解到,mvc包
的操作实际上是基于mvc.Application
类型的实例进行的。在初始化 mvc.Application 实例的时候,传入参数是一个 app.Party 的对象,即一个路由组。因此,一个mvc.Application
实例本质上就是一个路由分组。
然后,通过mvc.Application.Handle
方法,将 testController 类型的对象进行注册。实际上是将controller
依赖的对象(model、service
等)注入到controller
中、将Controller
中的方法转换成路由、将controller
中执行的结果渲染到view
的过程。
2.1 从 controller 到 ControllerActivator 的转换
在第一部分的代码示例中,当初始化了 mvc.Application 对象后,就是通过如下这行代码对 controller 中的方法进行转换的:
代码就一行,很简单。但就是这个 Handle 函数将 testController 中的方法转换成了路由,使用户可以通过对应的路径访问到 controller 中的方法。接下来我们看看该 Handle 方法中都做了哪些事情。
点击进入源代码,直到iris/mvc/mvc.go
文件中的handle
方法,如下图示所示。
该方法显示,首先将 controller 包装成了一个 ControllerActivator 对象 c。其结构体如下:
各字段含义如下:
injector:该 controller 中依赖的字段类型。即该 controller 结构体中有哪些字段。这个我们后面详细解释。
Value:该字段即在一开始 new 出来的 controller 的对象值。比如 new(testController)的值。
Typ:该字段是 controller 的具体类型。比如上面示例中的 testController 类型。
routes:controller 中每个函数名对应的具体的路由。
然后该对象 c 做了 3 件事情:执行对象 c 的 BeforeActivation 方法(如果 controller 中定义了该方法)、activate 方法和 AfterActivation 方法(如果定义了该方法)。我们先跳过 BeforeActivation 和 AfterActivation 方法,重点看下 activate 方法。
2.2 ControllerActivator 的 activate 方法
c.activate
方法的源码如下图所示,主要是 parseMethods 方法,根据名字也能猜到是要解析方法了。
我们再进入 c.parseMethods 方法的源代码,如下:
这里的逻辑也很简单,先是获取该类型(即controller
的结构体类型,例如示例中的testController
类型)的方法个数,然后依次遍历所有的方法,并对每个方法进行解析,也就是代码中的c.parseMethod(m)
方法。
接下来进入到c.parseMethod(m)
的代码逻辑中,看看该方法做了些什么。
这里看到,通过parseMethod
解析出来了请求的方法名称 httpMethod(例如 GET、POST 等)、请求的路径 httpPath。然后再通过 c.Handle 函数将对应的请求方法和请求路径以及方法名注册成 iris 的常规路由。
到这里我们先总结一下 controller 转换的过程:
首先,先将 controller 对象封装成 ControllerActivator 对象。该对象包含了 controller 类型的值以及具体的 controller 数据类型。
其次,通过 reflect 包获取该 controller 类型的有哪些方法,并依次遍历解析这些方法。
然后,根据方法名称解析出标准的 HTTP 请求的方法以及请求路径,即代码中的 parseMethord 函数。
最后,将该请求方法以及请求路径转换成 iris 常规的路由,即代码中的 c.Handle 函数。
因为请求方法和请求路径是根据 controller 中的方法名称解析出来的,所以开发人员在给 controller 的方法的命名时,需要遵循以下规则:
controller 中的方法必须采用驼峰式命名,且首字母必须大小。
parseMethod 会大写字母为分割符,将方法名分割成多个单词。例如。GetHome 会分割成 Get、Home 两个单词。方法名 HOME,则会被分割成 H、O、M、E 四个单词。具体实现算法可查看源代码methodLexer.reset函数。
方法名的第一个单词必须是 http 请求的方法名。有效的请求方法为 GET、HEAD、POST、PUT、PATCH、DELETE、CONNECT、OPTIONS、TRACE。具体的实现可查看源码请求方法校验逻辑。
从方法名的第二个单词开始,使用
"/"
符号将各个单词连接成对应的请求路径。所以方法名实际上是由请求方法+请求路径模式组成的。
例如在 testController 中有如下 GetMyHome 方法,那么,对应的请求路径是/my/home
。请求http://localhost:8080/my/home
就会执行 testController 的 GetMyHome 方法的逻辑。
在方法名中可以通过
By
可以在路由中增加动态参数。
例如在 testController 中有如下方法 GetByHome(username string),则对应的请求路径会被转换成 /{param1:string}/home。请求 http://localhost:8080/yufuzi/home 就能访问到 testController 中的 GetByHome 函数的逻辑,并且在该方法中username
的变量值就是路径中的yufizi
。如下:
若 By 在方法名的中间位置,一个 By 只对应一个动态参数;若 By 在方法名的最后,则方法中的所有输入参数都被解析成路径中的动态参数。
示例一:By 在方法名中间位置 GetByHome 方法名中有两个参数,那么,访问该路径http://localhost:8080/yufuzi/home
时就会报 panic。如下:
**示例二:**By 在方法名最后位置 GetHomeBy 方法名中有两个参数,则会转换成对应的路径 /home/{param1:string}/{param2:int}
。如下:
以上是对 controller 中方法的解析以及命名时需要遵守的规则。接下来,我们继续看如何将 controller 的方法转换成具体的路由请求处理器。
2.3 ControllerActivator 的 Handle 方法 -- 将 controller 中的方法转换成请求处理器
我们知道 iris 的标准路由处理器是type Handler func(*Context)类型
的这一个函数。那在 mvc 中,是如何将 controller 中的方法转换成这样标准的请求处理器类型的呢?
这个转换主要在 ControllerActivator.parseMethod 方法的第二部分的功能:ControllerActivator.Handle。进入该函数的源代码,直到 ControllerActivator.handleMany 函数,如下:
在 handleMany 函数中主要做了两件事:
将函数转换成请求处理器。即 c.handlerOf 函数做的事情
将请求处理注册成标准的路由。即 c.app.Router.HandleMany 函数做的事情。
这里我们主要看c.handlerOf
是如何将函数转换请求处理器的。至于注册成标准路由,大家可以参考这篇 iris 路由相关的文章:深入理解iris路由的底层实现原理。
2.4 将 controller 的函数转换成标准的请求处理器类型
处理该功能的是逻辑在handlerOf
函数中。下面是handlerOf
的源代码,我们看到主要做了两件事:一是 attachInjector 函数;一个是 injector 中的 MethodHandler 函数。
实际将 controller 的函数转换成请求处理器的是在 injector 的 MethodHandler 中进行的,即返回的 handler 对象。而 injector 就是在第一步的 c.attachInjector 函数中构造出来的。
那么,c.injector 是什么呢?下面我们看下其对应的结构体,如下图:
可以看出来,injector 实际上是对 controller 具体类型及其对象的描述。还是以 testController 为例,在 Struct.ptrType 就是代表 testController 这种数据类型;Struct.ptrValue 代表的是 testController 对象值;bindings 代表的是在 testController 中有哪些字段值。比如 testController 中要是定义如下:
那么,testController
对象就需要绑定 userModel 类型的值。在实例化testController
时,需要将一个具体的userModel
类型的值赋值给model
变量,这样在testController
的方法中就能引用该变量从数据源中读取数据。而这种依赖就是保存在bingdings
中的。
我们主要看第二部分,c.injector.MethodHandler
将函数转换成路由处理器类型。点击进入源码,直到/iris/hero/handler.go
文件的makeHandler
函数,如下所示(注:这里为了突出最终的返回值,省略了一些代码)。
首先,makeHandler
返回值就是一个context.Handler
类型的函数。在函数体内有 3 部分:获取controller
函数的输入参数值、通过v.Call
函数调用controller
实际的函数体、通过dispatchFuncResult
分发函数的返回值。
到此,就把controller
中的函数转换成了标准的路由处理器类型context.Handler
。并且,只有 controller 中的方法名的第一个单词是 HTTP 标准的请求方法(GET、HEAD、POST、PUT、PATCH、DELETE、CONNECT、OPTIONS、TRACE)时,才会将该方法自动注册成对应的路由。
在了解了 mvc 的实现原理后,下一篇我们讲解 mvc 的高级使用,敬请期待。
特别推荐:一个专注 go 项目实战、项目中踩坑经验及避坑指南、各种好玩的 go 工具的公众号,「Go 学堂」,专注实用性,非常值得大家关注。点击下方公众号卡片,直接关注。关注送《100 个 go 常见的错误》pdf 文档。
版权声明: 本文为 InfoQ 作者【Go学堂】的原创文章。
原文链接:【http://xie.infoq.cn/article/5d9e9f5caac8cbd978eb2d4e8】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论